Commit-Nachricht für RISE V2G

- Signature creation and verification has been fixed and works now properly. Has been tested against various industry OEMs and suppliers.
SecurityUtils.java class was substantially revised as part of fixing the signature creation and verification process and extended signature logging

- Introduced a new abstract method setMandatoryFieldsForFailedRes() in ServerState which makes sure that for each response the mandatory fields are set properly according to the standard in case a FAILED response code is sent

- Introduced a fix in WaitForAuthorizationReq which makes sure that a possible challenge is only checked for if the chosen payment option is „Contract“ (and TLS is used)

- Saves now the payment option selected by the EVCC (in state WaitForPaymentServiceSelectionReq) in the SECCs communication session for later reference

- To avoid possible race conditions, the transport layer threads needed to be started AFTER the SECC session handler has been initialized. Otherwise the situation might occur that the UDPServer is receiving a UDP client packet and tries to access the MessageHandler object before this object has been created by the SECC session handler.

- generateCertificates.sh Shell-Script now includes variables to ease the setting of certificates’ validity periods. Renaming of some certificates (e.g. provLeaf.cert -> cpsLeaf.cert) and keys. All certificates are now also generated in DER encoded form. MOSubCA2 private key is now also created as a PKCS#8 DER encoded file as RISE V2G needs this format to read the private key from file and sign SalesTariffs.

- EVCCConfig.properties and SECCConfig.properties have additional property „SignatureVerificationLog“ which enables extended logging information to debug signature creation and verification processes

- Catched a (theoretically) possible ClassCastException in WaitForChargeParameterDiscoveryRes in case the EVCC signalizes an EnergyTransferMode (AC or DC) which does not fit to the EVSEChargeParameter sent by the SECC later on

- Made sure that by default the sum of the individual time intervals described in the PMaxSchedule and SalesTariff provided in the ChargeParameterDiscoveryRes message are equal to 24 hours as demanded by a requirement.

- Renaming of some methods in IBackendInterface for clarity and consistency

- Provided a correctly formatted EVSE-ID in DummyACEVSEController and DummyDCEVSEController

- Corrected the service name of CertificateUpdate service and CertificateInstallation service to „Service“ according to the standard

- Edited GlobalValues so that the string value returned for each global value only needs to be edited once in this file (less error prone)
This commit is contained in:
Marc Mültin 2017-07-20 15:50:43 +02:00
parent 4838729761
commit 6b366610bc
50 changed files with 1253 additions and 468 deletions

1
.gitignore vendored
View File

@ -11,3 +11,4 @@
*/bin
.settings
.project
RISE-V2G-Certificates/testing-symposia

View File

@ -14,10 +14,32 @@
# Previously created certificates should have been provided with the respective release of the RISE V2G project for testing purposes. However, certain certificates might not be valid any more in which case you need to create new certificates.
# 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 15118. For example, OpenSSL 0.9.8 does not come with SHA-2 for SHA-256 signature algorithms.
# 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 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)
# Author: Marc Mültin (marc.mueltin@v2g-clarity.com)
# Some variables to create different outcomes of the PKI for testing purposes. Change the validity periods (given in number of days) to test
# - valid certificates (e.g. contract certificate or Sub-CA certificate)
# - expired certificates (e.g. contract certificate or Sub-CA certificates) -> you need to reset your system time to the past to create expired certificates
# - a to be updated contract certificate
validity_contract_cert=730
validity_mo_subca1_cert=1460
validity_mo_subca2_cert=1460
validity_oem_prov_cert=1460
validity_oem_subca1_cert=1460
validity_oem_subca2_cert=1460
validity_cps_leaf_cert=90
validity_cps_subca1_cert=1460
validity_cps_subca2_cert=730
validity_secc_cert=60
validity_cpo_subca1_cert=1460
validity_cpo_subca2_cert=365
validity_v2g_root_cert=3650
validity_oem_root_cert=3650
validity_mo_root_cert=3650
# 0) Create directories if not yet existing
mkdir -p certs
@ -32,151 +54,155 @@ mkdir -p privateKeys
# - for key of length 256 bit to be used for digital signatures -> -name secp256r1
# - with symmetric encryption AES 128 bit -> -aes128
# - and the passphrase for the private key provided in a file -> -passout file:passphrase.txt
openssl ecparam -genkey -name secp256r1 | openssl ec -out privateKeys/v2gRootCA.key -aes128 -passout file:passphrase.txt
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl ecparam -genkey -name secp256r1 | /usr/local/Cellar/openssl/1.0.2h_1/bin/openssl ec -out privateKeys/v2gRootCA.key -aes128 -passout file:passphrase.txt
# 1.2) Create a
# - new -> -new
# - self-signed certificate -> -new -x509 (and -out v2gRootCA.pem)
# - valid for 40 years -> -days 14600
# - with signature algorithm sha256 -> -sha256
# - with previously created private key -> -key privateKeys/v2gRootCA.key
# - and configuration data provided -> -config configs/v2gRootCA.cnf
# - and configuration data provided -> -config configs/v2gRootCACert.cnf
# - with extensions specified in section [ext] -> -extensions ext
openssl req -new -x509 -days 14600 -sha256 -key privateKeys/v2gRootCA.key -set_serial 01 -passin file:passphrase.txt -config configs/v2gRootCA.cnf -extensions ext -out certs/v2gRootCA.pem
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl req -new -x509 -days $validity_v2g_root_cert -sha256 -key privateKeys/v2gRootCA.key -set_serial 01 -passin file:passphrase.txt -config configs/v2gRootCACert.cnf -extensions ext -out certs/v2gRootCA.pem
# 2) Create an intermediate CPO sub-CA certificate which is directly signed by the V2GRootCA certificate
# 2.1) Create a private key (same procedure as for V2GRootCA certificate)
openssl ecparam -genkey -name secp256r1 | openssl ec -out privateKeys/cpoSub1CA.key -aes128 -passout file:passphrase.txt
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl ecparam -genkey -name secp256r1 | /usr/local/Cellar/openssl/1.0.2h_1/bin/openssl ec -out privateKeys/cpoSubCA1.key -aes128 -passout file:passphrase.txt
# 2.2) Create a
# - new Certificate Signing Request (CSR) -> -new (and -out cpoSub1CA.csr)
# - with previously created private key -> -key privateKeys/cpoSub1CA.key
# - and configuration data provided -> -config configs/cpoSub1CA.cnf
# - new Certificate Signing Request (CSR) -> -new (and -out cpoSubCA1.csr)
# - with previously created private key -> -key privateKeys/cpoSubCA1.key
# - and configuration data provided -> -config configs/cpoSubCA1Cert.cnf
# - with extensions specified in section [ext] -> -extensions ext
openssl req -new -key privateKeys/cpoSub1CA.key -passin file:passphrase.txt -config configs/cpoSub1CA.cnf -extensions ext -out csrs/cpoSub1CA.csr
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl req -new -key privateKeys/cpoSubCA1.key -passin file:passphrase.txt -config configs/cpoSubCA1Cert.cnf -extensions ext -out csrs/cpoSubCA1.csr
# 2.3) Create a
# - certificate for the CPOSub1CA -> x509
# - with the previously created CSR -> -in csrs/cpoSub1CA.csr
# - certificate for the CPOSubCA1 -> x509
# - with the previously created CSR -> -in csrs/cpoSubCA1.csr
# - signed by the V2GRootCA's private key -> -signkey privateKeys/v2gRootCA.key
# - with a validity of 4 years -> -days 1460
openssl x509 -req -in csrs/cpoSub1CA.csr -extfile configs/cpoSub1CA.cnf -extensions ext -CA certs/v2gRootCA.pem -CAkey privateKeys/v2gRootCA.key -set_serial 02 -passin file:passphrase.txt -days 1460 -out certs/cpoSub1CA.pem
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl x509 -req -in csrs/cpoSubCA1.csr -extfile configs/cpoSubCA1Cert.cnf -extensions ext -CA certs/v2gRootCA.pem -CAkey privateKeys/v2gRootCA.key -set_serial 02 -passin file:passphrase.txt -days $validity_cpo_subca1_cert -out certs/cpoSubCA1.pem
# 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
# 3) Create a second intermediate CPO sub-CA certificate just the way the previous intermedia certificate was created which is directly signed by the CPOSubCA1
# Differences to CPOSubCA1
# - 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 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
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl ecparam -genkey -name secp256r1 | /usr/local/Cellar/openssl/1.0.2h_1/bin/openssl ec -out privateKeys/cpoSubCA2.key -aes128 -passout file:passphrase.txt
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl req -new -key privateKeys/cpoSubCA2.key -passin file:passphrase.txt -config configs/cpoSubCA2Cert.cnf -extensions ext -out csrs/cpoSubCA2.csr
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl x509 -req -in csrs/cpoSubCA2.csr -extfile configs/cpoSubCA2Cert.cnf -extensions ext -CA certs/cpoSubCA1.pem -CAkey privateKeys/cpoSubCA1.key -set_serial 03 -passin file:passphrase.txt -days $validity_cpo_subca2_cert -out certs/cpoSubCA2.pem
# 4) Create an SECCCert certificate which is the leaf certificate belonging to the charging station which authenticates itself to the EVCC during a TLS handshake, signed by CPOSub2CA certificate
# Differences to CPOSub1CA and CPOSub2CA
# 4) Create an SECCCert certificate which is the leaf certificate belonging to the charging station which authenticates itself to the EVCC during a TLS handshake, signed by CPOSubCA2 certificate
# Differences to CPOSubCA1 and CPOSubCA2
# - 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 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
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl ecparam -genkey -name secp256r1 | /usr/local/Cellar/openssl/1.0.2h_1/bin/openssl ec -out privateKeys/seccCert.key -aes128 -passout file:passphrase.txt
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl req -new -key privateKeys/seccCert.key -passin file:passphrase.txt -config configs/seccCert.cnf -extensions ext -out csrs/seccCert.csr
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl x509 -req -in csrs/seccCert.csr -extfile configs/seccCert.cnf -extensions ext -CA certs/cpoSubCA2.pem -CAkey privateKeys/cpoSubCA2.key -set_serial 04 -passin file:passphrase.txt -days $validity_secc_cert -out certs/seccCert.pem
# Concatenate the intermediate CAs into one file intermediateCAs.pem
# IMPORTANT: Concatenate in such a way that the chain leads from the leaf certificate to the root (excluding), this means here: first parameter of the cat command is the intermediate CA's certificate which signs the leaf certificate (in this case cpoSub2CA.pem). Otherwise the Java method getCertificateChain() which is called on the keystore will only return the leaf certificate!
cat certs/cpoSub2CA.pem certs/cpoSub1CA.pem > certs/intermediateCPOCAs.pem
# IMPORTANT: Concatenate in such a way that the chain leads from the leaf certificate to the root (excluding), this means here: first parameter of the cat command is the intermediate CA's certificate which signs the leaf certificate (in this case cpoSubCA2.pem). Otherwise the Java method getCertificateChain() which is called on the keystore will only return the leaf certificate!
cat certs/cpoSubCA2.pem certs/cpoSubCA1.pem > certs/intermediateCPOCAs.pem
# Put the seccCertificate, the private key of the seccCertificate as well as the intermediate CAs in a pkcs12 container.
# IMPORTANT: It is necessary to put all necessary intermediate CAs directly into the PKCS12 container (with the -certfile switch), instead of later on mporting the PKCS12 containter only holding the leaf certificate (seccCert) and its private key and additionally importing the intermediate CAs via the keytool command (TLS handshake will fail).
# This is the reason why we need two password files (passphrase.txt and passphrase2.txt). Possibly the passphrase.txt file resource is locked before being accessed a second time within the same command? See also http://rt.openssl.org/Ticket/Display.html?id=3168&user=guest&pass=guest
# IMPORTANT: It is necessary to put all necessary intermediate CAs directly into the PKCS12 container (with the -certfile switch), instead of later on iporting the PKCS12 containter only holding the leaf certificate (seccCert) and its private key and additionally importing the intermediate CAs via the keytool command (TLS handshake will fail).
# This is the reason why we need two password files (passphrase.txt and passphrase2.txt). Possibly the passphrase.txt file resource is locked before being accessed a second time within the same command? See also http://rt./usr/local/Cellar/openssl/1.0.2h_1/bin/openssl.org/Ticket/Display.html?id=3168&user=guest&pass=guest
# The -name switch corresponds to the -alias switch in the keytool command later on
openssl pkcs12 -export -inkey privateKeys/seccCert.key -in certs/seccCert.pem -certfile certs/intermediateCPOCAs.pem -aes128 -passin file:passphrase.txt -passout file:passphrase2.txt -name secc_cert -out certs/seccCert.p12
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl pkcs12 -export -inkey privateKeys/seccCert.key -in certs/seccCert.pem -certfile certs/intermediateCPOCAs.pem -aes128 -passin file:passphrase.txt -passout file:passphrase2.txt -name secc_cert -out certs/cpoCertChain.p12
# 5) Create a self-signed OEMRootCA certificate (validity is up to the OEM, this example applies the same validity as the V2GRootCA)
openssl ecparam -genkey -name secp256r1 | openssl ec -out privateKeys/oemRootCA.key -aes128 -passout file:passphrase.txt
openssl req -new -x509 -days 14600 -sha256 -key privateKeys/oemRootCA.key -set_serial 05 -passin file:passphrase.txt -config configs/oemRootCA.cnf -extensions ext -out certs/oemRootCA.pem
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl ecparam -genkey -name secp256r1 | /usr/local/Cellar/openssl/1.0.2h_1/bin/openssl ec -out privateKeys/oemRootCA.key -aes128 -passout file:passphrase.txt
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl req -new -x509 -days $validity_oem_root_cert -sha256 -key privateKeys/oemRootCA.key -set_serial 05 -passin file:passphrase.txt -config configs/oemRootCACert.cnf -extensions ext -out certs/oemRootCA.pem
# 6) Create an intermediate OEM sub-CA certificate which is directly signed by the OEMRootCA certificate (validity is up to the OEM, this example applies the same validity as the CPOSubCA1)
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl ecparam -genkey -name secp256r1 | /usr/local/Cellar/openssl/1.0.2h_1/bin/openssl ec -out privateKeys/oemSubCA1.key -aes128 -passout file:passphrase.txt
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl req -new -key privateKeys/oemSubCA1.key -passin file:passphrase.txt -config configs/oemSubCA1Cert.cnf -extensions ext -out csrs/oemSubCA1.csr
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl x509 -req -in csrs/oemSubCA1.csr -extfile configs/oemSubCA1Cert.cnf -extensions ext -CA certs/oemRootCA.pem -CAkey privateKeys/oemRootCA.key -set_serial 06 -passin file:passphrase.txt -days $validity_oem_subca1_cert -out certs/oemSubCA1.pem
# 6) Create an intermediate OEM sub-CA certificate which is directly signed by the OEMRootCA certificate (validity is up to the OEM, this example applies the same validity as the CPOSub1CA)
openssl ecparam -genkey -name secp256r1 | openssl ec -out privateKeys/oemSub1CA.key -aes128 -passout file:passphrase.txt
openssl req -new -key privateKeys/oemSub1CA.key -passin file:passphrase.txt -config configs/oemSub1CA.cnf -extensions ext -out csrs/oemSub1CA.csr
openssl x509 -req -in csrs/oemSub1CA.csr -extfile configs/oemSub1CA.cnf -extensions ext -CA certs/oemRootCA.pem -CAkey privateKeys/oemRootCA.key -set_serial 06 -passin file:passphrase.txt -days 1460 -out certs/oemSub1CA.pem
# 7) Create a second intermediate OEM sub-CA certificate which is directly signed by the OEMSub1CA certificate (validity is up to the OEM, this example applies the same validity as the CPOSub2CA)
openssl ecparam -genkey -name secp256r1 | openssl ec -out privateKeys/oemSub2CA.key -aes128 -passout file:passphrase.txt
openssl req -new -key privateKeys/oemSub2CA.key -passin file:passphrase.txt -config configs/oemSub2CA.cnf -extensions ext -out csrs/oemSub2CA.csr
openssl x509 -req -in csrs/oemSub2CA.csr -extfile configs/oemSub2CA.cnf -extensions ext -CA certs/oemSub1CA.pem -CAkey privateKeys/oemSub1CA.key -set_serial 07 -passin file:passphrase.txt -days 1460 -out certs/oemSub2CA.pem
# 7) Create a second intermediate OEM sub-CA certificate which is directly signed by the OEMSubCA1 certificate (validity is up to the OEM, this example applies the same validity as the CPOSubCA2)
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl ecparam -genkey -name secp256r1 | /usr/local/Cellar/openssl/1.0.2h_1/bin/openssl ec -out privateKeys/oemSubCA2.key -aes128 -passout file:passphrase.txt
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl req -new -key privateKeys/oemSubCA2.key -passin file:passphrase.txt -config configs/oemSubCA2Cert.cnf -extensions ext -out csrs/oemSubCA2.csr
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl x509 -req -in csrs/oemSubCA2.csr -extfile configs/oemSubCA2Cert.cnf -extensions ext -CA certs/oemSubCA1.pem -CAkey privateKeys/oemSubCA1.key -set_serial 07 -passin file:passphrase.txt -days $validity_oem_subca2_cert -out certs/oemSubCA2.pem
# 8) Create an OEM provisioning certificate which is the leaf certificate belonging to the OEM certificate chain (used for contract certificate installation)
openssl ecparam -genkey -name secp256r1 | openssl ec -out privateKeys/oemProvCert.key -aes128 -passout file:passphrase.txt
openssl req -new -key privateKeys/oemProvCert.key -passin file:passphrase.txt -config configs/oemProvCert.cnf -extensions ext -out csrs/oemProvCert.csr
openssl x509 -req -in csrs/oemProvCert.csr -extfile configs/oemProvCert.cnf -extensions ext -CA certs/oemSub2CA.pem -CAkey privateKeys/oemSub2CA.key -set_serial 08 -passin file:passphrase.txt -days 60 -out certs/oemProvCert.pem
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
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl ecparam -genkey -name secp256r1 | /usr/local/Cellar/openssl/1.0.2h_1/bin/openssl ec -out privateKeys/oemProvCert.key -aes128 -passout file:passphrase.txt
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl req -new -key privateKeys/oemProvCert.key -passin file:passphrase.txt -config configs/oemProvCert.cnf -extensions ext -out csrs/oemProvCert.csr
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl x509 -req -in csrs/oemProvCert.csr -extfile configs/oemProvCert.cnf -extensions ext -CA certs/oemSubCA2.pem -CAkey privateKeys/oemSubCA2.key -set_serial 08 -passin file:passphrase.txt -days $validity_oem_prov_cert -out certs/oemProvCert.pem
cat certs/oemSubCA2.pem certs/oemSubCA1.pem > certs/intermediateOEMCAs.pem
/usr/local/Cellar/openssl/1.0.2h_1/bin/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/oemCertChain.p12
# 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
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl ecparam -genkey -name secp256r1 | /usr/local/Cellar/openssl/1.0.2h_1/bin/openssl ec -out privateKeys/moRootCA.key -aes128 -passout file:passphrase.txt
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl req -new -x509 -days $validity_mo_root_cert -sha256 -key privateKeys/moRootCA.key -set_serial 09 -passin file:passphrase.txt -config configs/moRootCACert.cnf -extensions ext -out certs/moRootCA.pem
# 10) Create an intermediate MO sub-CA certificate which is directly signed by the MORootCA certificate (validity is up to the MO, this example applies the same validity as the CPOSubCA1)
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl ecparam -genkey -name secp256r1 | /usr/local/Cellar/openssl/1.0.2h_1/bin/openssl ec -out privateKeys/moSubCA1.key -aes128 -passout file:passphrase.txt
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl req -new -key privateKeys/moSubCA1.key -passin file:passphrase.txt -config configs/moSubCA1Cert.cnf -extensions ext -out csrs/moSubCA1.csr
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl x509 -req -in csrs/moSubCA1.csr -extfile configs/moSubCA1Cert.cnf -extensions ext -CA certs/moRootCA.pem -CAkey privateKeys/moRootCA.key -set_serial 10 -passin file:passphrase.txt -days $validity_mo_subca1_cert -out certs/moSubCA1.pem
# 10) Create an intermediate MO sub-CA certificate which is directly signed by the MORootCA certificate (validity is up to the MO, this example applies the same validity as the CPOSub1CA)
openssl ecparam -genkey -name secp256r1 | openssl ec -out privateKeys/moSub1CA.key -aes128 -passout file:passphrase.txt
openssl req -new -key privateKeys/moSub1CA.key -passin file:passphrase.txt -config configs/moSub1CA.cnf -extensions ext -out csrs/moSub1CA.csr
openssl x509 -req -in csrs/moSub1CA.csr -extfile configs/moSub1CA.cnf -extensions ext -CA certs/moRootCA.pem -CAkey privateKeys/moRootCA.key -set_serial 10 -passin file:passphrase.txt -days 1460 -out certs/moSub1CA.pem
# 11) Create a second intermediate MO sub-CA certificate which is directly signed by the MOSub1CA certificate (validity is up to the MO, this example applies the same validity as the CPOSub2CA)
openssl ecparam -genkey -name secp256r1 | openssl ec -out privateKeys/moSub2CA.key -aes128 -passout file:passphrase.txt
openssl req -new -key privateKeys/moSub2CA.key -passin file:passphrase.txt -config configs/moSub2CA.cnf -extensions ext -out csrs/moSub2CA.csr
openssl x509 -req -in csrs/moSub2CA.csr -extfile configs/moSub2CA.cnf -extensions ext -CA certs/moSub1CA.pem -CAkey privateKeys/moSub1CA.key -set_serial 11 -passin file:passphrase.txt -days 1460 -out certs/moSub2CA.pem
# 11) Create a second intermediate MO sub-CA certificate which is directly signed by the MOSubCA1 certificate (validity is up to the MO, this example applies the same validity as the CPOSubCA2)
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl ecparam -genkey -name secp256r1 | /usr/local/Cellar/openssl/1.0.2h_1/bin/openssl ec -out privateKeys/moSubCA2.key -aes128 -passout file:passphrase.txt
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl req -new -key privateKeys/moSubCA2.key -passin file:passphrase.txt -config configs/moSubCA2Cert.cnf -extensions ext -out csrs/moSubCA2.csr
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl x509 -req -in csrs/moSubCA2.csr -extfile configs/moSubCA2Cert.cnf -extensions ext -CA certs/moSubCA1.pem -CAkey privateKeys/moSubCA1.key -set_serial 11 -passin file:passphrase.txt -days $validity_mo_subca2_cert -out certs/moSubCA2.pem
# 12) Create a contract certificate which is the leaf certificate belonging to the MO certificate chain (used for contract certificate installation)
# Validity can be between 4 weeks and 2 years (restricted by the contract lifetime), for testing purposes the validity will be set to 2 years
openssl ecparam -genkey -name secp256r1 | openssl ec -out privateKeys/contractCert.key -aes128 -passout file:passphrase.txt
openssl req -new -key privateKeys/contractCert.key -passin file:passphrase.txt -config configs/contractCert.cnf -extensions ext -out csrs/contractCert.csr
openssl x509 -req -in csrs/contractCert.csr -extfile configs/contractCert.cnf -extensions ext -CA certs/moSub2CA.pem -CAkey privateKeys/moSub2CA.key -set_serial 12 -passin file:passphrase.txt -days 730 -out certs/contractCert.pem
cat certs/moSub2CA.pem certs/moSub1CA.pem > certs/intermediateMOCAs.pem
openssl pkcs12 -export -inkey privateKeys/contractCert.key -in certs/contractCert.pem -certfile certs/intermediateMOCAs.pem -aes128 -passin file:passphrase.txt -passout file:passphrase2.txt -name contract_cert -out certs/contractCert.p12
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl ecparam -genkey -name secp256r1 | /usr/local/Cellar/openssl/1.0.2h_1/bin/openssl ec -out privateKeys/contractCert.key -aes128 -passout file:passphrase.txt
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl req -new -key privateKeys/contractCert.key -passin file:passphrase.txt -config configs/contractCert.cnf -extensions ext -out csrs/contractCert.csr
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl x509 -req -in csrs/contractCert.csr -extfile configs/contractCert.cnf -extensions ext -CA certs/moSubCA2.pem -CAkey privateKeys/moSubCA2.key -set_serial 12 -passin file:passphrase.txt -days $validity_contract_cert -out certs/contractCert.pem
cat certs/moSubCA2.pem certs/moSubCA1.pem > certs/intermediateMOCAs.pem
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl pkcs12 -export -inkey privateKeys/contractCert.key -in certs/contractCert.pem -certfile certs/intermediateMOCAs.pem -aes128 -passin file:passphrase.txt -passout file:passphrase2.txt -name contract_cert -out certs/moCertChain.p12
# 13) Create an intermediate provisioning service sub-CA certificate which is directly signed by the V2GRootCA certificate
openssl ecparam -genkey -name secp256r1 | openssl ec -out privateKeys/provSub1CA.key -aes128 -passout file:passphrase.txt
openssl req -new -key privateKeys/provSub1CA.key -passin file:passphrase.txt -config configs/provSub1CA.cnf -extensions ext -out csrs/provSub1CA.csr
openssl x509 -req -in csrs/provSub1CA.csr -extfile configs/provSub1CA.cnf -extensions ext -CA certs/v2gRootCA.pem -CAkey privateKeys/v2gRootCA.key -set_serial 13 -passin file:passphrase.txt -days 1460 -out certs/provSub1CA.pem
# 14) Create a second intermediate provisioning sub-CA certificate which is directly signed by the ProvSub1CA certificate (validity 1 - 2 years, we make it 2 years)
openssl ecparam -genkey -name secp256r1 | openssl ec -out privateKeys/provSub2CA.key -aes128 -passout file:passphrase.txt
openssl req -new -key privateKeys/provSub2CA.key -passin file:passphrase.txt -config configs/provSub2CA.cnf -extensions ext -out csrs/provSub2CA.csr
openssl x509 -req -in csrs/provSub2CA.csr -extfile configs/provSub2CA.cnf -extensions ext -CA certs/provSub1CA.pem -CAkey privateKeys/provSub1CA.key -set_serial 14 -passin file:passphrase.txt -days 730 -out certs/provSub2CA.pem
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl ecparam -genkey -name secp256r1 | /usr/local/Cellar/openssl/1.0.2h_1/bin/openssl ec -out privateKeys/cpsSubCA1.key -aes128 -passout file:passphrase.txt
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl req -new -key privateKeys/cpsSubCA1.key -passin file:passphrase.txt -config configs/cpsSubCA1Cert.cnf -extensions ext -out csrs/cpsSubCA1.csr
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl x509 -req -in csrs/cpsSubCA1.csr -extfile configs/cpsSubCA1Cert.cnf -extensions ext -CA certs/v2gRootCA.pem -CAkey privateKeys/v2gRootCA.key -set_serial 13 -passin file:passphrase.txt -days $validity_cps_subca1_cert -out certs/cpsSubCA1.pem
# 14) Create a second intermediate provisioning sub-CA certificate which is directly signed by the CPSSubCA1 certificate (validity 1 - 2 years, we make it 2 years)
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl ecparam -genkey -name secp256r1 | /usr/local/Cellar/openssl/1.0.2h_1/bin/openssl ec -out privateKeys/cpsSubCA2.key -aes128 -passout file:passphrase.txt
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl req -new -key privateKeys/cpsSubCA2.key -passin file:passphrase.txt -config configs/cpsSubCA2Cert.cnf -extensions ext -out csrs/cpsSubCA2.csr
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl x509 -req -in csrs/cpsSubCA2.csr -extfile configs/cpsSubCA2Cert.cnf -extensions ext -CA certs/cpsSubCA1.pem -CAkey privateKeys/cpsSubCA1.key -set_serial 14 -passin file:passphrase.txt -days $validity_cps_subca2_cert -out certs/cpsSubCA2.pem
# 15) Create a provisioning service certificate which is the leaf certificate belonging to the provisioning certificate chain (used for contract certificate installation)
# Validity can be between 2 - 3 months, we make it 3 months
openssl ecparam -genkey -name secp256r1 | openssl ec -out privateKeys/provServiceCert.key -aes128 -passout file:passphrase.txt
openssl req -new -key privateKeys/provServiceCert.key -passin file:passphrase.txt -config configs/provServiceCert.cnf -extensions ext -out csrs/provServiceCert.csr
openssl x509 -req -in csrs/provServiceCert.csr -extfile configs/provServiceCert.cnf -extensions ext -CA certs/provSub2CA.pem -CAkey privateKeys/provSub2CA.key -set_serial 15 -passin file:passphrase.txt -days 90 -out certs/provServiceCert.pem
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
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl ecparam -genkey -name secp256r1 | /usr/local/Cellar/openssl/1.0.2h_1/bin/openssl ec -out privateKeys/cpsLeafCert.key -aes128 -passout file:passphrase.txt
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl req -new -key privateKeys/cpsLeafCert.key -passin file:passphrase.txt -config configs/cpsLeafCert.cnf -extensions ext -out csrs/cpsLeafCert.csr
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl x509 -req -in csrs/cpsLeafCert.csr -extfile configs/cpsLeafCert.cnf -extensions ext -CA certs/cpsSubCA2.pem -CAkey privateKeys/cpsSubCA2.key -set_serial 15 -passin file:passphrase.txt -days $validity_cps_leaf_cert -out certs/cpsLeafCert.pem
cat certs/cpsSubCA2.pem certs/cpsSubCA1.pem > certs/intermediateCPSCAs.pem
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl pkcs12 -export -inkey privateKeys/cpsLeafCert.key -in certs/cpsLeafCert.pem -certfile certs/intermediateCPSCAs.pem -aes128 -passin file:passphrase.txt -passout file:passphrase2.txt -name cps_leaf_cert -out certs/cpsCertChain.p12
# 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
# 16) 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)
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl x509 -inform PEM -in certs/v2gRootCA.pem -outform DER -out certs/v2gRootCA.der
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl x509 -inform PEM -in certs/cpsSubCA1.pem -outform DER -out certs/cpsSubCA1.der
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl x509 -inform PEM -in certs/cpsSubCA2.pem -outform DER -out certs/cpsSubCA2.der
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl x509 -inform PEM -in certs/cpsLeafCert.pem -outform DER -out certs/cpsLeafCert.der
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl x509 -inform PEM -in certs/cpoSubCA1.pem -outform DER -out certs/cpoSubCA1.der
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl x509 -inform PEM -in certs/cpoSubCA2.pem -outform DER -out certs/cpoSubCA2.der
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl x509 -inform PEM -in certs/seccCert.pem -outform DER -out certs/seccCert.der
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl x509 -inform PEM -in certs/oemRootCA.pem -outform DER -out certs/oemRootCA.der
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl x509 -inform PEM -in certs/oemSubCA1.pem -outform DER -out certs/oemSubCA1.der
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl x509 -inform PEM -in certs/oemSubCA2.pem -outform DER -out certs/oemSubCA2.der
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl x509 -inform PEM -in certs/oemProvCert.pem -outform DER -out certs/oemProvCert.der
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl x509 -inform PEM -in certs/moRootCA.pem -outform DER -out certs/moRootCA.der
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl x509 -inform PEM -in certs/moSubCA1.pem -outform DER -out certs/moSubCA1.der
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl x509 -inform PEM -in certs/moSubCA2.pem -outform DER -out certs/moSubCA2.der
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl x509 -inform PEM -in certs/contractCert.pem -outform DER -out certs/contractCert.der
# Since the intermediate certificates need to be in PEM format when putting them in a PKCS12 container and the resulting PKCS12 file is a binary format, it might be sufficient. Otherwise, I have currently no idea how to covert the intermediate certificates in DER without running into problems when creating the PKCS12 container.
# 17) In case you want the private keys in PKCS#8 file format and DER encoded, use this command. Especially necessary for the private key of MOSubCA2 in RISE V2G
/usr/local/Cellar/openssl/1.0.2h_1/bin/openssl pkcs8 -topk8 -in privateKeys/moSubCA2.key -inform PEM -passin file:passphrase.txt -passout file:passphrase2.txt -outform DER -out privateKeys/moSubCA2.pkcs8.der
# 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 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
keytool -importkeystore -srckeystore certs/seccCert.p12 -srcstoretype pkcs12 -srcstorepass:file passphrase.txt -srcalias secc_cert -destalias secc_cert -destkeystore keystores/seccKeystore.jks -storepass:file passphrase.txt -noprompt
# XX.4) keystore for the EVCC which needs to hold the OEMSub1CA, OEMSub2CA and OEMProvCert certificates
keytool -importkeystore -srckeystore certs/oemProvCert.p12 -srcstoretype pkcs12 -srcstorepass:file passphrase.txt -srcalias oem_prov_cert -destalias oem_prov_cert -destkeystore keystores/evccKeystore.jks -storepass:file passphrase.txt -noprompt
# XX.1) truststore for the EVCC which needs to hold the V2GRootCA certificate (the EVCC does not verify the received contract 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.der -storepass:file passphrase.txt -noprompt
# XX.2) truststore for the SECC which needs to hold the V2GRootCA certificate and the MORootCA which signed the MOSubCA1 (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 MOSubCA1 could instead be signed by a V2GRootCA.
keytool -import -keystore keystores/seccTruststore.jks -alias v2g_root_ca -file certs/v2gRootCA.der -storepass:file passphrase.txt -noprompt
keytool -import -keystore keystores/seccTruststore.jks -alias mo_root_ca -file certs/moRootCA.der -storepass:file passphrase.txt -noprompt
# XX.3) keystore for the SECC which needs to hold the CPOSubCA1, CPOSubCA2, and SECCCert certificates
keytool -importkeystore -srckeystore certs/cpoCertChain.p12 -srcstoretype pkcs12 -srcstorepass:file passphrase.txt -srcalias secc_cert -destalias secc_cert -destkeystore keystores/seccKeystore.jks -storepass:file passphrase.txt -noprompt
# XX.4) keystore for the EVCC which needs to hold the OEMSubCA1, OEMSubCA2, and OEMProvCert certificates
keytool -importkeystore -srckeystore certs/oemCertChain.p12 -srcstoretype pkcs12 -srcstorepass:file passphrase.txt -srcalias oem_prov_cert -destalias oem_prov_cert -destkeystore keystores/evccKeystore.jks -storepass:file passphrase.txt -noprompt
# Side notes for OCSP stapling in Java: see http://openjdk.java.net/jeps/8046321

View File

@ -31,10 +31,10 @@ NetworkInterface = en0
TLSSecurity = true
# Contract certificate update timespan
# Contract certificate update time span
#-------------------------------------
#
# Integer value defining the timespan in days which precedes the expiration of a contract certificate
# Integer value defining the time span in days which precedes the expiration of a contract certificate
# and during which an update of the contract certificate needs to be performed
ContractCertificateUpdateTimespan = 14
@ -68,7 +68,7 @@ RequestedPaymentOption =
# - DC_extended
# - DC_combo_core
# - DC_unique
RequestedEnergyTransferMode = DC_core
RequestedEnergyTransferMode = AC_three_phase_core
# XML representation of messages
@ -79,4 +79,15 @@ RequestedEnergyTransferMode = DC_core
# - 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
XMLRepresentationOfMessages = true
# Extended logging of signature verification
#-------------------------------------------
#
# Possible values:
# - true
# - false
# If this value is set to 'true', extended logging will be printed upon verification of signatures (for debugging purposes)
# If no correct value is provided here, 'false' will be chosen
SignatureVerificationLog = true

View File

@ -259,7 +259,7 @@ public abstract class ClientState extends State {
* 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");
authorizationReq.setId("ID1");
}
return authorizationReq;

View File

@ -50,7 +50,7 @@ public class WaitForAuthorizationRes extends ClientState {
AuthorizationReqType authorizationReq = getAuthorizationReq(null);
getXMLSignatureRefElements().put(
authorizationReq.getId(),
SecurityUtils.generateDigest(authorizationReq, false));
SecurityUtils.generateDigest(authorizationReq));
// Set signing private key
setSignaturePrivateKey(SecurityUtils.getPrivateKey(

View File

@ -11,7 +11,6 @@
package org.eclipse.risev2g.evcc.states;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.util.HashMap;
import org.eclipse.risev2g.evcc.session.V2GCommunicationSessionEVCC;
@ -78,21 +77,21 @@ public class WaitForCertificateInstallationRes extends ClientState {
HashMap<String, byte[]> verifyXMLSigRefElements = new HashMap<String, byte[]>();
verifyXMLSigRefElements.put(
certificateInstallationRes.getContractSignatureCertChain().getId(),
SecurityUtils.generateDigest(certificateInstallationRes.getContractSignatureCertChain(), false));
SecurityUtils.generateDigest(certificateInstallationRes.getContractSignatureCertChain()));
verifyXMLSigRefElements.put(
certificateInstallationRes.getContractSignatureEncryptedPrivateKey().getId(),
SecurityUtils.generateDigest(certificateInstallationRes.getContractSignatureEncryptedPrivateKey(), false));
SecurityUtils.generateDigest(certificateInstallationRes.getContractSignatureEncryptedPrivateKey()));
verifyXMLSigRefElements.put(
certificateInstallationRes.getDHpublickey().getId(),
SecurityUtils.generateDigest(certificateInstallationRes.getDHpublickey(), false));
SecurityUtils.generateDigest(certificateInstallationRes.getDHpublickey()));
verifyXMLSigRefElements.put(
certificateInstallationRes.getEMAID().getId(),
SecurityUtils.generateDigest(certificateInstallationRes.getEMAID(), false));
SecurityUtils.generateDigest(certificateInstallationRes.getEMAID()));
ECPublicKey ecPublicKey = (ECPublicKey) SecurityUtils.getCertificate(
certificateInstallationRes.getSAProvisioningCertificateChain().getCertificate())
.getPublicKey();
if (!SecurityUtils.verifySignature(signature, verifyXMLSigRefElements, ecPublicKey)) {
if (!SecurityUtils.verifySignature(
signature,
verifyXMLSigRefElements,
certificateInstallationRes.getSAProvisioningCertificateChain().getCertificate())) {
return false;
}

View File

@ -11,7 +11,6 @@
package org.eclipse.risev2g.evcc.states;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.util.HashMap;
import org.eclipse.risev2g.evcc.session.V2GCommunicationSessionEVCC;
@ -78,21 +77,21 @@ public class WaitForCertificateUpdateRes extends ClientState {
HashMap<String, byte[]> verifyXMLSigRefElements = new HashMap<String, byte[]>();
verifyXMLSigRefElements.put(
certificateUpdateRes.getContractSignatureCertChain().getId(),
SecurityUtils.generateDigest(certificateUpdateRes.getContractSignatureCertChain(), false));
SecurityUtils.generateDigest(certificateUpdateRes.getContractSignatureCertChain()));
verifyXMLSigRefElements.put(
certificateUpdateRes.getContractSignatureEncryptedPrivateKey().getId(),
SecurityUtils.generateDigest(certificateUpdateRes.getContractSignatureEncryptedPrivateKey(), false));
SecurityUtils.generateDigest(certificateUpdateRes.getContractSignatureEncryptedPrivateKey()));
verifyXMLSigRefElements.put(
certificateUpdateRes.getDHpublickey().getId(),
SecurityUtils.generateDigest(certificateUpdateRes.getDHpublickey(), false));
SecurityUtils.generateDigest(certificateUpdateRes.getDHpublickey()));
verifyXMLSigRefElements.put(
certificateUpdateRes.getEMAID().getId(),
SecurityUtils.generateDigest(certificateUpdateRes.getEMAID(), false));
SecurityUtils.generateDigest(certificateUpdateRes.getEMAID()));
ECPublicKey ecPublicKey = (ECPublicKey) SecurityUtils.getCertificate(
certificateUpdateRes.getSAProvisioningCertificateChain().getCertificate())
.getPublicKey();
if (!SecurityUtils.verifySignature(signature, verifyXMLSigRefElements, ecPublicKey)) {
if (!SecurityUtils.verifySignature(
signature,
verifyXMLSigRefElements,
certificateUpdateRes.getSAProvisioningCertificateChain().getCertificate())) {
return false;
}

View File

@ -11,7 +11,6 @@
package org.eclipse.risev2g.evcc.states;
import java.security.cert.X509Certificate;
import java.security.interfaces.ECPublicKey;
import java.util.HashMap;
import java.util.List;
@ -28,7 +27,6 @@ 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;
@ -53,14 +51,20 @@ public class WaitForChargeParameterDiscoveryRes extends ClientState {
} else {
// Check for the EVSENotification
EVSENotificationType evseNotification = null;
if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("AC"))
try {
if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("AC"))
evseNotification = ((ACEVSEChargeParameterType) chargeParameterDiscoveryRes
.getEVSEChargeParameter().getValue())
.getACEVSEStatus().getEVSENotification();
else
evseNotification = ((DCEVSEChargeParameterType) chargeParameterDiscoveryRes
.getEVSEChargeParameter().getValue())
.getDCEVSEStatus().getEVSENotification();
else
evseNotification = ((DCEVSEChargeParameterType) chargeParameterDiscoveryRes
.getEVSEChargeParameter().getValue())
.getDCEVSEStatus().getEVSENotification();
} catch (ClassCastException e) {
return new TerminateSession("Sent EVSEChargeParameter do not match requested energy transfer mode " +
getCommSessionContext().getRequestedEnergyTransferMode().toString());
}
if (evseNotification.equals(EVSENotificationType.STOP_CHARGING)) {
getLogger().debug("The EVSE requested to stop the charging process");
@ -81,8 +85,8 @@ public class WaitForChargeParameterDiscoveryRes extends ClientState {
// If TLS is used, verify each sales tariff (if present) with the mobility operator sub 2 certificate
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.");
getLogger().warn("The SalesTariff will be ignored for the charge process due to "
+ "failed signature verification during TLS communication.");
deleteUnverifiedSalesTariffs(saSchedules);
}
@ -128,12 +132,12 @@ public class WaitForChargeParameterDiscoveryRes extends ClientState {
/*
* Some important requirements:
*
* 1. In case of PnC, and if a Tariff Table is used by the secondary actor, the secondary actor SHALL
* 1. In case of PnC, and if a SalesTariff 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
* 2. If the EVCC treats the SalesTariff as invalid, it shall ignore the SalesTariff, i.e. the
* behavior of the EVCC shall be the same as if no SalesTariff was received. Furthermore, the
* EVCC MAY close the connection. It then may reopen the connection again.
*/
@ -160,25 +164,26 @@ public class WaitForChargeParameterDiscoveryRes extends ClientState {
verifyXMLSigRefElements.put(
saScheduleTuple.getSalesTariff().getId(),
SecurityUtils.generateDigest(saScheduleTuple.getSalesTariff(), false));
SecurityUtils.generateDigest(saScheduleTuple.getSalesTariff()));
}
if (salesTariffCounter > 0) {
X509Certificate moSub2Certificate = SecurityUtils.getMOSub2Certificate(
X509Certificate moSubCA2Certificate = SecurityUtils.getMOSubCA2Certificate(
GlobalValues.EVCC_KEYSTORE_FILEPATH.toString());
if (moSub2Certificate == null) {
getLogger().error("No MOSub2Certificate found, signature of sales tariff could therefore not be verified");
if (moSubCA2Certificate == null) {
getLogger().error("No MOSubCA2 certificate found, signature of SalesTariff could therefore not be verified");
return false;
} else {
ECPublicKey ecPublicKey = (ECPublicKey) moSub2Certificate.getPublicKey();
if (!SecurityUtils.verifySignature(signature, verifyXMLSigRefElements, ecPublicKey)) {
if (!SecurityUtils.verifySignature(signature, verifyXMLSigRefElements, moSubCA2Certificate)) {
getLogger().warn("Verification of SalesTariff failed using certificate with distinguished name '" +
moSubCA2Certificate.getSubjectX500Principal().getName() + "'");
return false;
}
}
}
if (ignoredSalesTariffs > 0) {
getLogger().info("Sales tariffs could not be verified because of missing signature and will therefore be ignored");
getLogger().info("SalesTariffs could not be verified because of missing signature and will therefore be ignored");
return false;
}

View File

@ -58,7 +58,7 @@ public class WaitForChargingStatusRes extends ClientState {
// Set xml reference element
getXMLSignatureRefElements().put(
meteringReceiptReq.getId(),
SecurityUtils.generateDigest(meteringReceiptReq, false));
SecurityUtils.generateDigest(meteringReceiptReq));
// Set signing private key
setSignaturePrivateKey(SecurityUtils.getPrivateKey(

View File

@ -51,7 +51,7 @@ public class WaitForCurrentDemandRes extends ClientState {
meteringReceiptReq.setSessionID(getCommSessionContext().getSessionID());
// Set xml reference element
getXMLSignatureRefElements().put(meteringReceiptReq.getId(), SecurityUtils.generateDigest(meteringReceiptReq, false));
getXMLSignatureRefElements().put(meteringReceiptReq.getId(), SecurityUtils.generateDigest(meteringReceiptReq));
// Set signing private key
setSignaturePrivateKey(SecurityUtils.getPrivateKey(

View File

@ -10,6 +10,8 @@
*******************************************************************************/
package org.eclipse.risev2g.evcc.states;
import java.util.Base64;
import org.eclipse.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import org.eclipse.risev2g.shared.enumerations.GlobalValues;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
@ -41,12 +43,12 @@ public class WaitForPaymentDetailsRes extends ClientState {
if (paymentDetailsRes.getGenChallenge() == null) {
return new TerminateSession("GenChallenge not provided in PaymentDetailsRes");
} else {
// Set xml reference element
AuthorizationReqType authorizationReq = getAuthorizationReq(paymentDetailsRes.getGenChallenge());
// Set xml reference element
getXMLSignatureRefElements().put(
authorizationReq.getId(),
SecurityUtils.generateDigest(authorizationReq, false));
SecurityUtils.generateDigest(authorizationReq));
// Set signing private key
setSignaturePrivateKey(SecurityUtils.getPrivateKey(

View File

@ -78,7 +78,7 @@ public class WaitForPaymentServiceSelectionRes extends ClientState {
* 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.setId("ID1");
certInstallationReq.setListOfRootCertificateIDs(
SecurityUtils.getListOfRootCertificateIDs(
GlobalValues.EVCC_TRUSTSTORE_FILEPATH.toString(),
@ -88,7 +88,7 @@ public class WaitForPaymentServiceSelectionRes extends ClientState {
evccKeyStore, GlobalValues.ALIAS_OEM_PROV_CERTIFICATE.toString()).getCertificate());
// Set xml reference element
getXMLSignatureRefElements().put(certInstallationReq.getId(), SecurityUtils.generateDigest(certInstallationReq, false));
getXMLSignatureRefElements().put(certInstallationReq.getId(), SecurityUtils.generateDigest(certInstallationReq));
// Set signing private key
setSignaturePrivateKey(SecurityUtils.getPrivateKey(
@ -117,7 +117,7 @@ public class WaitForPaymentServiceSelectionRes extends ClientState {
* 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.setId("ID1");
certificateUpdateReq.setListOfRootCertificateIDs(
SecurityUtils.getListOfRootCertificateIDs(
GlobalValues.EVCC_TRUSTSTORE_FILEPATH.toString(),
@ -126,7 +126,7 @@ public class WaitForPaymentServiceSelectionRes extends ClientState {
// Set xml reference element
getXMLSignatureRefElements().put(
certificateUpdateReq.getId(),
SecurityUtils.generateDigest(certificateUpdateReq, false));
SecurityUtils.generateDigest(certificateUpdateReq));
// Set signing private key
setSignaturePrivateKey(SecurityUtils.getPrivateKey(

View File

@ -95,6 +95,12 @@ public class WaitForServiceDiscoveryRes extends ClientState {
private boolean useVAS(ServiceDiscoveryResType serviceDiscoveryRes) {
if (serviceDiscoveryRes.getServiceList() != null &&
getCommSessionContext().getTransportLayerClient() instanceof TLSClient) {
getLogger().debug("List of offered value added services: ");
for (ServiceType service : serviceDiscoveryRes.getServiceList().getService()) {
getLogger().debug("ID = " + service.getServiceID() + ", name = " + service.getServiceName());
}
// Check if certificate service is needed
if (isCertificateServiceOffered(serviceDiscoveryRes.getServiceList())) {
getCommSessionContext().setContractCertStatus(SecurityUtils.getContractCertificateStatus());

View File

@ -72,5 +72,16 @@ PrivateEnvironment = false
# - false
# If this value is set to 'true', the EXICodec will print each message's XML representation (for debugging purposes)
# If no correct value is provided here, 'false' will be chosen
XMLRepresentationOfMessages = false
XMLRepresentationOfMessages = true
# Extended logging of signature verification
#-------------------------------------------
#
# Possible values:
# - true
# - false
# If this value is set to 'true', extended logging will be printed upon verification of signatures (for debugging purposes)
# If no correct value is provided here, 'false' will be chosen
SignatureVerificationLog = true

View File

@ -91,7 +91,7 @@ public class DummyBackendInterface implements IBackendInterface {
// PMaxSchedule
// IMPORTANT: check that you do not add more pMax entries than parameter maxEntriesSAScheduleTuple
PMaxScheduleType pMaxSchedule = new PMaxScheduleType();
pMaxSchedule.getPMaxScheduleEntry().add(createPMaxScheduleEntry("3", (short) 11, 0, 7200L));
pMaxSchedule.getPMaxScheduleEntry().add(createPMaxScheduleEntry("3", (short) 11, 0, 86400L));
/*
* SalesTariff (add some meaningful things)
@ -107,7 +107,7 @@ public class DummyBackendInterface implements IBackendInterface {
* 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.setId("ID1");
salesTariff.setSalesTariffID((short) 1);
salesTariff.getSalesTariffEntry().add(createSalesTariffEntry(0L, (short) 1));
salesTariff.getSalesTariffEntry().add(createSalesTariffEntry(1800L, (short) 4));
@ -127,7 +127,7 @@ public class DummyBackendInterface implements IBackendInterface {
if (saScheduleTuple.getSalesTariff() != null) {
xmlSignatureRefElements.put(
salesTariff.getId(),
SecurityUtils.generateDigest(salesTariff, false));
SecurityUtils.generateDigest(salesTariff));
}
return saScheduleList;
@ -154,8 +154,7 @@ public class DummyBackendInterface implements IBackendInterface {
pMaxValue.setValue(pMax);
RelativeTimeIntervalType pMaxTimeInterval = new RelativeTimeIntervalType();
pMaxTimeInterval.setStart(0);
pMaxTimeInterval.setDuration(7200L); // 2 hours
pMaxTimeInterval.setStart(start);
PMaxScheduleEntryType pMaxScheduleEntry = new PMaxScheduleEntryType();
pMaxScheduleEntry.setTimeInterval(new JAXBElement<RelativeTimeIntervalType>(
@ -177,13 +176,13 @@ public class DummyBackendInterface implements IBackendInterface {
@Override
public CertificateChainType getContractCertificateChain() {
return SecurityUtils.getCertificateChain("./contractCert.p12");
return SecurityUtils.getCertificateChain("./moCertChain.p12");
}
@Override
public ECPrivateKey getContractCertificatePrivateKey() {
KeyStore keyStore = SecurityUtils.getPKCS12KeyStore(
"./contractCert.p12",
"./moCertChain.p12",
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString());
ECPrivateKey privateKey = SecurityUtils.getPrivateKey(keyStore);
@ -195,23 +194,21 @@ public class DummyBackendInterface implements IBackendInterface {
@Override
public ECPrivateKey getSAProvisioningCertificatePrivateKey() {
public ECPrivateKey getCPSLeafPrivateKey() {
KeyStore keyStore = SecurityUtils.getPKCS12KeyStore(
"./provServiceCert.p12",
"./cpsCertChain.p12",
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString());
ECPrivateKey privateKey = SecurityUtils.getPrivateKey(keyStore);
if (privateKey == null)
getLogger().error("No private key available from provisioning service keystore");
getLogger().error("No private key available from Certificate Provisioning Service keystore");
return privateKey;
}
@Override
public ECPrivateKey getMOSubCA2CertificatePrivateKey() {
ECPrivateKey privateKey = SecurityUtils.getPrivateKey(
"./moSub2CA.key",
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString());
public ECPrivateKey getMOSubCA2PrivateKey() {
ECPrivateKey privateKey = SecurityUtils.getPrivateKey("./moSubCA2.pkcs8.der");
if (privateKey == null)
getLogger().error("No private key available from MO Sub-CA 2 PKCS#8 file");
@ -221,8 +218,8 @@ public class DummyBackendInterface implements IBackendInterface {
@Override
public CertificateChainType getSAProvisioningCertificateChain() {
return SecurityUtils.getCertificateChain("./provServiceCert.p12");
public CertificateChainType getCPSCertificateChain() {
return SecurityUtils.getCertificateChain("./cpsCertChain.p12");
}

View File

@ -49,19 +49,19 @@ public interface IBackendInterface {
* the provisioning certificate and possible intermediate certificates (sub CAs) included.
* @return Certificate chain for provisioning certificate
*/
public CertificateChainType getSAProvisioningCertificateChain();
public CertificateChainType getCPSCertificateChain();
/**
* Provides the private key belonging to the SA provisioning certificate.
* @return PrivateKey of the SA provisioning certificate
*/
public ECPrivateKey getSAProvisioningCertificatePrivateKey();
public ECPrivateKey getCPSLeafPrivateKey();
/**
* Provides the private key belonging to the MO Sub-CA 2 certificate (signature of SalesTariff).
* @return PrivateKey of the MO Sub-CA 2 certificate
*/
public ECPrivateKey getMOSubCA2CertificatePrivateKey();
public ECPrivateKey getMOSubCA2PrivateKey();
}

View File

@ -34,7 +34,7 @@ public class DummyACEVSEController implements IACEVSEController {
@Override
public String getEvseID() {
return "EVSEID-0";
return "DE*V2G*E12345";
}
@Override

View File

@ -40,7 +40,7 @@ public class DummyDCEVSEController implements IDCEVSEController {
@Override
public String getEvseID() {
return "EVSEID-0";
return "DE*V2G*E12345";
}
@ -88,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.VALID);
dcEvseStatus.setEVSEIsolationStatus(IsolationLevelType.INVALID);
return dcEvseStatus;
}

View File

@ -34,17 +34,25 @@ public class StartSECC {
} else {
Thread udpServerThread = new Thread(udpServer);
udpServerThread.setName("UDPServerThread");
udpServerThread.start();
Thread tcpServerThread = new Thread(tcpServer);
tcpServerThread.setName("TCPServerThread");
tcpServerThread.start();
Thread tlsServerThread = new Thread(tlsServer);
tlsServerThread.setName("TLSServerThread");
tlsServerThread.start();
// All transport layer threads need to be initialized before initializing the SECC session handler.
V2GCommunicationSessionHandlerSECC sessionHandler = new V2GCommunicationSessionHandlerSECC();
/*
* To avoid possible race conditions, the transport layer threads need to be started AFTER the SECC
* session handler has been initialized. Otherwise the situation might occur that the UDPServer is
* receiving a UDP client packet and tries to access the MessageHandler object before this object has
* been created by the SECC session handler.
*/
udpServerThread.start();
tcpServerThread.start();
tlsServerThread.start();
}
}
}

View File

@ -39,8 +39,14 @@ public class ForkState extends ServerState {
@Override
public ReactionToIncomingMessage processIncomingMessage(Object message) {
V2GMessage v2gMessageReq = (V2GMessage) message;
V2GMessages incomingMessage =
V2GMessages incomingMessage = null;
try {
incomingMessage =
V2GMessages.fromValue(v2gMessageReq.getBody().getBodyElement().getValue().getClass().getSimpleName());
} catch (NullPointerException e) {
return new TerminateSession("No valid V2GMessage received");
}
if (allowedRequests.contains(incomingMessage)) {
State newState = getCommSessionContext().getStates().get(incomingMessage);
@ -78,4 +84,9 @@ public class ForkState extends ServerState {
return allowedRequests;
}
@Override
protected void setMandatoryFieldsForFailedRes() {
// Nothing to do here
}
}

View File

@ -173,5 +173,14 @@ public abstract class ServerState extends State {
this.responseCode = responseCode;
return true;
}
/**
* In case a FAILED response code is sent, the mandatory fields still need to be set with minimum required values,
* otherwise the EVCC's EXI decoder will raise an error.
*
* @param response The respective response message whose mandatory fields are to be set
*/
protected abstract void setMandatoryFieldsForFailedRes();
}

View File

@ -10,7 +10,6 @@
*******************************************************************************/
package org.eclipse.risev2g.secc.states;
import java.security.interfaces.ECPublicKey;
import java.util.Arrays;
import java.util.HashMap;
@ -21,6 +20,7 @@ import org.eclipse.risev2g.shared.utils.SecurityUtils;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.AuthorizationReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.AuthorizationResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.EVSEProcessingType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PaymentOptionType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.SignatureType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.V2GMessage;
@ -58,30 +58,45 @@ public class WaitForAuthorizationReq extends ServerState {
}
} else {
getLogger().error("Response code '" + authorizationRes.getResponseCode() + "' will be sent");
setMandatoryFieldsForFailedRes();
}
}
} else {
setMandatoryFieldsForFailedRes();
}
return getSendMessage(authorizationRes, V2GMessages.NONE);
}
public boolean isResponseCodeOK(AuthorizationReqType authorizationReq, SignatureType signature) {
if (getCommSessionContext().getSelectedPaymentOption().equals(PaymentOptionType.EXTERNAL_PAYMENT)) {
if (authorizationReq.getGenChallenge() != null)
getLogger().warn("EVCC sent a challenge parameter but " + PaymentOptionType.EXTERNAL_PAYMENT +
" has been chosen. The challenge parameter should not be present and will be ignored.");
return true;
}
if (!Arrays.equals(authorizationReq.getGenChallenge(), getCommSessionContext().getGenChallenge())) {
authorizationRes.setResponseCode(ResponseCodeType.FAILED_CHALLENGE_INVALID);
return false;
}
// Only try to verify the signature in case we use a TLS connection
if (getCommSessionContext().isTlsConnection()) {
/*
* Only try to verify the signature in case we use a TLS connection and 'Contract' has been chosen as payment
* method. If EIM has been chosen, then no contract certificate chain and not challenge will be sent by the EV,
* but TLS is possible with both EIM and PnC.
*/
if (getCommSessionContext().isTlsConnection() &&
getCommSessionContext().getSelectedPaymentOption().equals(PaymentOptionType.CONTRACT)) {
// Verify signature
HashMap<String, byte[]> verifyXMLSigRefElements = new HashMap<String, byte[]>();
verifyXMLSigRefElements.put(authorizationReq.getId(), SecurityUtils.generateDigest(authorizationReq, false));
verifyXMLSigRefElements.put(authorizationReq.getId(), SecurityUtils.generateDigest(authorizationReq));
ECPublicKey ecPublicKey = (ECPublicKey) SecurityUtils.getCertificate(
getCommSessionContext().getContractSignatureCertChain().getCertificate())
.getPublicKey();
if (!SecurityUtils.verifySignature(signature, verifyXMLSigRefElements, ecPublicKey)) {
if (!SecurityUtils.verifySignature(
signature,
verifyXMLSigRefElements,
getCommSessionContext().getContractSignatureCertChain().getCertificate())) {
authorizationRes.setResponseCode(ResponseCodeType.FAILED_SIGNATURE_ERROR);
return false;
}
@ -97,5 +112,11 @@ public class WaitForAuthorizationReq extends ServerState {
public void setAuthorizationFinished(boolean authorizationFinished) {
this.authorizationFinished = authorizationFinished;
}
@Override
protected void setMandatoryFieldsForFailedRes() {
authorizationRes.setEVSEProcessing(EVSEProcessingType.FINISHED);
}
}

View File

@ -57,7 +57,9 @@ public class WaitForCableCheckReq extends ServerState {
cableCheckRes.setEVSEProcessing(EVSEProcessingType.ONGOING);
return getSendMessage(cableCheckRes, V2GMessages.CABLE_CHECK_REQ);
}
}
} else {
setMandatoryFieldsForFailedRes();
}
return getSendMessage(cableCheckRes, V2GMessages.NONE);
}
@ -71,4 +73,13 @@ public class WaitForCableCheckReq extends ServerState {
this.evseProcessingFinished = evseProcessingFinished;
}
@Override
protected void setMandatoryFieldsForFailedRes() {
cableCheckRes.setEVSEProcessing(EVSEProcessingType.FINISHED);
cableCheckRes.setDCEVSEStatus(
((IDCEVSEController) getCommSessionContext().getDCEvseController()).getDCEVSEStatus(EVSENotificationType.NONE)
);
}
}

View File

@ -22,6 +22,8 @@ import org.eclipse.risev2g.shared.v2gMessages.msgDef.CertificateChainType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.CertificateInstallationReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.CertificateInstallationResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ContractSignatureEncryptedPrivateKeyType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.DiffieHellmanPublickeyType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.EMAIDType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.SignatureType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.V2GMessage;
@ -46,10 +48,10 @@ public class WaitForCertificateInstallationReq extends ServerState {
certificateInstallationReq,
saContractCertificateChain,
v2gMessageReq.getHeader().getSignature())) {
// The ECDH (elliptic curve Diffie Hellman) key pair is also needed for the generation of the shared secret
KeyPair ecdhKeyPair = SecurityUtils.getECDHKeyPair();
if (ecdhKeyPair == null) {
getLogger().error("ECDH keypair could not be generated");
// The EC key pair is also needed for the generation of the shared secret
KeyPair ecKeyPair = SecurityUtils.getECKeyPair();
if (ecKeyPair == null) {
getLogger().error("EC keypair could not be generated");
return null;
}
@ -57,7 +59,7 @@ public class WaitForCertificateInstallationReq extends ServerState {
ContractSignatureEncryptedPrivateKeyType encryptedContractCertPrivateKey =
SecurityUtils.encryptContractCertPrivateKey(
(ECPublicKey) SecurityUtils.getCertificate(certificateInstallationReq.getOEMProvisioningCert()).getPublicKey(),
ecdhKeyPair,
ecKeyPair,
getCommSessionContext().getBackendInterface().getContractCertificatePrivateKey());
certificateInstallationRes.setContractSignatureCertChain(saContractCertificateChain);
@ -70,33 +72,36 @@ public class WaitForCertificateInstallationReq extends ServerState {
certificateInstallationRes.getContractSignatureCertChain().setId("id1"); // contractSignatureCertChain
certificateInstallationRes.setContractSignatureEncryptedPrivateKey(encryptedContractCertPrivateKey);
certificateInstallationRes.getContractSignatureEncryptedPrivateKey().setId("id2"); // contractSignatureEncryptedPrivateKey
certificateInstallationRes.setDHpublickey(SecurityUtils.getDHPublicKey(ecdhKeyPair));
certificateInstallationRes.setDHpublickey(SecurityUtils.getDHPublicKey(ecKeyPair));
certificateInstallationRes.getDHpublickey().setId("id3"); // dhPublicKey
certificateInstallationRes.setEMAID(SecurityUtils.getEMAID(saContractCertificateChain));
certificateInstallationRes.getEMAID().setId("id4"); // emaid
certificateInstallationRes.setSAProvisioningCertificateChain(
getCommSessionContext().getBackendInterface().getSAProvisioningCertificateChain());
getCommSessionContext().getBackendInterface().getCPSCertificateChain());
// Set xml reference elements
getXMLSignatureRefElements().put(
certificateInstallationRes.getContractSignatureCertChain().getId(),
SecurityUtils.generateDigest(certificateInstallationRes.getContractSignatureCertChain(), false));
SecurityUtils.generateDigest(certificateInstallationRes.getContractSignatureCertChain()));
getXMLSignatureRefElements().put(
certificateInstallationRes.getContractSignatureEncryptedPrivateKey().getId(),
SecurityUtils.generateDigest(certificateInstallationRes.getContractSignatureEncryptedPrivateKey(), false));
SecurityUtils.generateDigest(certificateInstallationRes.getContractSignatureEncryptedPrivateKey()));
getXMLSignatureRefElements().put(
certificateInstallationRes.getDHpublickey().getId(),
SecurityUtils.generateDigest(certificateInstallationRes.getDHpublickey(), false));
SecurityUtils.generateDigest(certificateInstallationRes.getDHpublickey()));
getXMLSignatureRefElements().put(
certificateInstallationRes.getEMAID().getId(),
SecurityUtils.generateDigest(certificateInstallationRes.getEMAID(), false));
SecurityUtils.generateDigest(certificateInstallationRes.getEMAID()));
// Set signing private key
setSignaturePrivateKey(getCommSessionContext().getBackendInterface().getSAProvisioningCertificatePrivateKey());
setSignaturePrivateKey(getCommSessionContext().getBackendInterface().getCPSLeafPrivateKey());
} else {
getLogger().error("Response code '" + certificateInstallationRes.getResponseCode() + "' will be sent");
setMandatoryFieldsForFailedRes();
}
} else {
setMandatoryFieldsForFailedRes();
}
return getSendMessage(certificateInstallationRes, V2GMessages.PAYMENT_DETAILS_REQ);
@ -106,7 +111,6 @@ public class WaitForCertificateInstallationReq extends ServerState {
CertificateInstallationReqType certificateInstallationReq,
CertificateChainType saContractCertificateChain,
SignatureType signature) {
// Check for FAILED_NoCertificateAvailable
if (saContractCertificateChain == null || saContractCertificateChain.getCertificate() == null) {
certificateInstallationRes.setResponseCode(ResponseCodeType.FAILED_NO_CERTIFICATE_AVAILABLE);
@ -128,15 +132,44 @@ public class WaitForCertificateInstallationReq extends ServerState {
// Verify signature
HashMap<String, byte[]> verifyXMLSigRefElements = new HashMap<String, byte[]>();
verifyXMLSigRefElements.put(certificateInstallationReq.getId(), SecurityUtils.generateDigest(certificateInstallationReq, false));
ECPublicKey ecPublicKey = (ECPublicKey) SecurityUtils.getCertificate(
certificateInstallationReq.getOEMProvisioningCert())
.getPublicKey();
if (!SecurityUtils.verifySignature(signature, verifyXMLSigRefElements, ecPublicKey)) {
verifyXMLSigRefElements.put(certificateInstallationReq.getId(), SecurityUtils.generateDigest(certificateInstallationReq));
if (!SecurityUtils.verifySignature(
signature,
verifyXMLSigRefElements,
certificateInstallationReq.getOEMProvisioningCert())) {
certificateInstallationRes.setResponseCode(ResponseCodeType.FAILED_SIGNATURE_ERROR);
return false;
}
return true;
}
@Override
protected void setMandatoryFieldsForFailedRes() {
CertificateChainType saProvisioningCertificateChain = new CertificateChainType();
saProvisioningCertificateChain.setCertificate(new byte[1]);
certificateInstallationRes.setSAProvisioningCertificateChain(saProvisioningCertificateChain);
CertificateChainType contractSignatureCertChain = new CertificateChainType();
contractSignatureCertChain.setCertificate(new byte[1]);
contractSignatureCertChain.setId("ID1");
certificateInstallationRes.setContractSignatureCertChain(contractSignatureCertChain);
ContractSignatureEncryptedPrivateKeyType contractSignatureEncryptedPrivateKey = new ContractSignatureEncryptedPrivateKeyType();
contractSignatureEncryptedPrivateKey.setValue(new byte[1]);
contractSignatureEncryptedPrivateKey.setId("ID2");
certificateInstallationRes.setContractSignatureEncryptedPrivateKey(contractSignatureEncryptedPrivateKey);
DiffieHellmanPublickeyType dhPublicKeyType = new DiffieHellmanPublickeyType();
dhPublicKeyType.setValue(new byte[1]);
dhPublicKeyType.setId("ID3");
certificateInstallationRes.setDHpublickey(dhPublicKeyType);
EMAIDType emaid = new EMAIDType();
emaid.setValue("DEV2G1234512345");
emaid.setId("ID4");
certificateInstallationRes.setEMAID(emaid);
}
}

View File

@ -23,6 +23,8 @@ import org.eclipse.risev2g.shared.v2gMessages.msgDef.CertificateChainType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.CertificateUpdateReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.CertificateUpdateResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ContractSignatureEncryptedPrivateKeyType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.DiffieHellmanPublickeyType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.EMAIDType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.SignatureType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.V2GMessage;
@ -49,7 +51,7 @@ public class WaitForCertificateUpdateReq extends ServerState {
contractCertificateChain,
v2gMessageReq.getHeader().getSignature())) {
// The ECDH (elliptic curve Diffie Hellman) key pair is also needed for the generation of the shared secret
KeyPair ecdhKeyPair = SecurityUtils.getECDHKeyPair();
KeyPair ecdhKeyPair = SecurityUtils.getECKeyPair();
if (ecdhKeyPair == null) {
getLogger().error("ECDH keypair could not be generated");
return null;
@ -76,7 +78,7 @@ public class WaitForCertificateUpdateReq extends ServerState {
certificateUpdateRes.getDHpublickey().setId("id3"); // dhPublicKey
certificateUpdateRes.setEMAID(SecurityUtils.getEMAID(contractCertificateChain));
certificateUpdateRes.getEMAID().setId("id4"); // emaid
certificateUpdateRes.setSAProvisioningCertificateChain(getCommSessionContext().getBackendInterface().getSAProvisioningCertificateChain());
certificateUpdateRes.setSAProvisioningCertificateChain(getCommSessionContext().getBackendInterface().getCPSCertificateChain());
// In case of negative response code, try at next charging (retryCounter = 0)
if (!certificateUpdateRes.getResponseCode().toString().startsWith("OK"))
@ -85,22 +87,25 @@ public class WaitForCertificateUpdateReq extends ServerState {
// Set xml reference elements
getXMLSignatureRefElements().put(
certificateUpdateRes.getContractSignatureCertChain().getId(),
SecurityUtils.generateDigest(certificateUpdateRes.getContractSignatureCertChain(), false));
SecurityUtils.generateDigest(certificateUpdateRes.getContractSignatureCertChain()));
getXMLSignatureRefElements().put(
certificateUpdateRes.getContractSignatureEncryptedPrivateKey().getId(),
SecurityUtils.generateDigest(certificateUpdateRes.getContractSignatureEncryptedPrivateKey(), false));
SecurityUtils.generateDigest(certificateUpdateRes.getContractSignatureEncryptedPrivateKey()));
getXMLSignatureRefElements().put(
certificateUpdateRes.getDHpublickey().getId(),
SecurityUtils.generateDigest(certificateUpdateRes.getDHpublickey(), false));
SecurityUtils.generateDigest(certificateUpdateRes.getDHpublickey()));
getXMLSignatureRefElements().put(
certificateUpdateRes.getEMAID().getId(),
SecurityUtils.generateDigest(certificateUpdateRes.getEMAID(), false));
SecurityUtils.generateDigest(certificateUpdateRes.getEMAID()));
// Set signing private key
setSignaturePrivateKey(getCommSessionContext().getBackendInterface().getSAProvisioningCertificatePrivateKey());
setSignaturePrivateKey(getCommSessionContext().getBackendInterface().getCPSLeafPrivateKey());
} else {
getLogger().error("Response code '" + certificateUpdateRes.getResponseCode() + "' will be sent");
setMandatoryFieldsForFailedRes();
}
} else {
setMandatoryFieldsForFailedRes();
}
return getSendMessage(certificateUpdateRes,
@ -146,15 +151,44 @@ public class WaitForCertificateUpdateReq extends ServerState {
// Verify signature
HashMap<String, byte[]> verifyXMLSigRefElements = new HashMap<String, byte[]>();
verifyXMLSigRefElements.put(certificateUpdateReq.getId(), SecurityUtils.generateDigest(certificateUpdateReq, false));
ECPublicKey ecPublicKey = (ECPublicKey) SecurityUtils.getCertificate(
certificateUpdateReq.getContractSignatureCertChain().getCertificate())
.getPublicKey();
if (!SecurityUtils.verifySignature(signature, verifyXMLSigRefElements, ecPublicKey)) {
verifyXMLSigRefElements.put(certificateUpdateReq.getId(), SecurityUtils.generateDigest(certificateUpdateReq));
if (!SecurityUtils.verifySignature(
signature,
verifyXMLSigRefElements,
certificateUpdateReq.getContractSignatureCertChain().getCertificate())) {
certificateUpdateRes.setResponseCode(ResponseCodeType.FAILED_SIGNATURE_ERROR);
return false;
}
return true;
}
@Override
protected void setMandatoryFieldsForFailedRes() {
CertificateChainType saProvisioningCertificateChain = new CertificateChainType();
saProvisioningCertificateChain.setCertificate(new byte[1]);
certificateUpdateRes.setSAProvisioningCertificateChain(saProvisioningCertificateChain);
CertificateChainType contractSignatureCertChain = new CertificateChainType();
contractSignatureCertChain.setCertificate(new byte[1]);
contractSignatureCertChain.setId("ID1");
certificateUpdateRes.setContractSignatureCertChain(contractSignatureCertChain);
ContractSignatureEncryptedPrivateKeyType contractSignatureEncryptedPrivateKey = new ContractSignatureEncryptedPrivateKeyType();
contractSignatureEncryptedPrivateKey.setValue(new byte[1]);
contractSignatureEncryptedPrivateKey.setId("ID2");
certificateUpdateRes.setContractSignatureEncryptedPrivateKey(contractSignatureEncryptedPrivateKey);
DiffieHellmanPublickeyType dhPublicKeyType = new DiffieHellmanPublickeyType();
dhPublicKeyType.setValue(new byte[1]);
dhPublicKeyType.setId("ID3");
certificateUpdateRes.setDHpublickey(dhPublicKeyType);
EMAIDType emaid = new EMAIDType();
emaid.setValue("DEV2G1234512345");
emaid.setId("ID4");
certificateUpdateRes.setEMAID(emaid);
}
}

View File

@ -106,8 +106,17 @@ public class WaitForChargeParameterDiscoveryReq extends ServerState {
chargeParameterDiscoveryRes.setSASchedules(
getSASchedulesAsJAXBElement(getCommSessionContext().getSaSchedules()));
// Set signing private key of Mobility Operator Sub-CA 2
setSignaturePrivateKey(getCommSessionContext().getBackendInterface().getMOSubCA2CertificatePrivateKey());
/*
* Note 3 of [V2G2-905] states:
* "If the secondary actor is unaware of which authentication mode is used during EVCC-SECC
* communication (EIM/ PnC), it can simply always sign the SalesTariff."
*
* Therefore, we do not check here if PnC is used but just always sign the SalesTariff.
* Without a real backend functionality, we must sign the SalesTariff by using the SecurityUtils
* class.
*/
//Set signing private key of Mobility Operator Sub-CA 2
setSignaturePrivateKey(getCommSessionContext().getBackendInterface().getMOSubCA2PrivateKey());
if (chargeParameterDiscoveryReq.getRequestedEnergyTransferMode().toString().startsWith("AC"))
return getSendMessage(chargeParameterDiscoveryRes, V2GMessages.POWER_DELIVERY_REQ);
@ -116,8 +125,11 @@ public class WaitForChargeParameterDiscoveryReq extends ServerState {
}
} else {
getLogger().error("Response code '" + chargeParameterDiscoveryRes.getResponseCode() + "' will be sent");
setMandatoryFieldsForFailedRes();
}
}
} else {
setMandatoryFieldsForFailedRes();
}
return getSendMessage(chargeParameterDiscoveryRes, V2GMessages.NONE);
}
@ -182,4 +194,12 @@ public class WaitForChargeParameterDiscoveryReq extends ServerState {
this.waitingForSchedule = waitingForSchedule;
}
@Override
protected void setMandatoryFieldsForFailedRes() {
chargeParameterDiscoveryRes.setEVSEProcessing(EVSEProcessingType.FINISHED);
chargeParameterDiscoveryRes.setEVSEChargeParameter(
((IACEVSEController) getCommSessionContext().getACEvseController()).getACEVSEChargeParameter());
}
}

View File

@ -42,7 +42,7 @@ public class WaitForChargingStatusReq extends ServerState {
*/
chargingStatusRes.setACEVSEStatus(
((IACEVSEController) getCommSessionContext().getACEvseController())
.getACEVSEStatus(EVSENotificationType.STOP_CHARGING)
.getACEVSEStatus(EVSENotificationType.NONE)
);
// Optionally indicate that the EVCC is required to send a MeteringReceiptReq message
@ -70,9 +70,21 @@ public class WaitForChargingStatusReq extends ServerState {
return getSendMessage(chargingStatusRes, V2GMessages.FORK);
}
}
} else {
setMandatoryFieldsForFailedRes();
}
return getSendMessage(chargingStatusRes, V2GMessages.NONE);
}
@Override
protected void setMandatoryFieldsForFailedRes() {
chargingStatusRes.setEVSEID(getCommSessionContext().getACEvseController().getEvseID());
chargingStatusRes.setSAScheduleTupleID((short) 1);
chargingStatusRes.setACEVSEStatus(((IACEVSEController) getCommSessionContext().getACEvseController())
.getACEVSEStatus(EVSENotificationType.NONE)
);
}
}

View File

@ -17,6 +17,8 @@ import org.eclipse.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.CurrentDemandReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.CurrentDemandResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.EVSENotificationType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PhysicalValueType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.UnitSymbolType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class WaitForCurrentDemandReq extends ServerState {
@ -50,7 +52,7 @@ public class WaitForCurrentDemandReq extends ServerState {
* Change EVSENotificationType to NONE if you want more than one charge loop iteration,
* but then make sure the EV is stopping the charge loop
*/
currentDemandRes.setDCEVSEStatus(evseController.getDCEVSEStatus(EVSENotificationType.STOP_CHARGING));
currentDemandRes.setDCEVSEStatus(evseController.getDCEVSEStatus(EVSENotificationType.NONE));
currentDemandRes.setEVSECurrentLimitAchieved(evseController.isEVSECurrentLimitAchieved());
currentDemandRes.setEVSEVoltageLimitAchieved(evseController.isEVSEVoltageLimitAchieved());
@ -66,7 +68,7 @@ public class WaitForCurrentDemandReq extends ServerState {
currentDemandRes.setSAScheduleTupleID(getCommSessionContext().getChosenSAScheduleTuple());
// TODO how to determine if a receipt is required or not?
currentDemandRes.setReceiptRequired(false);
currentDemandRes.setReceiptRequired(true);
if (currentDemandRes.isReceiptRequired()) {
return getSendMessage(currentDemandRes, V2GMessages.METERING_RECEIPT_REQ);
@ -78,9 +80,30 @@ public class WaitForCurrentDemandReq extends ServerState {
return getSendMessage(currentDemandRes, V2GMessages.FORK);
}
}
} else {
setMandatoryFieldsForFailedRes();
}
return getSendMessage(currentDemandRes, V2GMessages.NONE);
}
@Override
protected void setMandatoryFieldsForFailedRes() {
IDCEVSEController evseController = (IDCEVSEController) getCommSessionContext().getDCEvseController();
PhysicalValueType physicalValueType = new PhysicalValueType();
physicalValueType.setMultiplier(new Byte("0"));
physicalValueType.setUnit(UnitSymbolType.V); // does not matter which unit symbol if FAILED response is sent
physicalValueType.setValue((short) 1);
currentDemandRes.setDCEVSEStatus(evseController.getDCEVSEStatus(EVSENotificationType.NONE));
currentDemandRes.setEVSEPresentVoltage(physicalValueType);
currentDemandRes.setEVSEPresentCurrent(physicalValueType);
currentDemandRes.setEVSECurrentLimitAchieved(false);
currentDemandRes.setEVSEVoltageLimitAchieved(false);
currentDemandRes.setEVSEPowerLimitAchieved(false);
currentDemandRes.setEVSEID(evseController.getEvseID());
currentDemandRes.setSAScheduleTupleID((short) 1);
}
}

View File

@ -10,7 +10,6 @@
*******************************************************************************/
package org.eclipse.risev2g.secc.states;
import java.security.interfaces.ECPublicKey;
import java.util.Arrays;
import java.util.HashMap;
@ -20,7 +19,6 @@ import javax.xml.namespace.QName;
import org.eclipse.risev2g.secc.session.V2GCommunicationSessionSECC;
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.ACEVSEStatusType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.DCEVSEStatusType;
@ -50,28 +48,7 @@ public class WaitForMeteringReceiptReq extends ServerState {
(MeteringReceiptReqType) v2gMessageReq.getBody().getBodyElement().getValue();
if (isResponseCodeOK(meteringReceiptReq, v2gMessageReq.getHeader().getSignature())) {
if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("AC")) {
/*
* The MiscUtils method getJAXBElement() cannot be used here because of the difference in the
* class name (ACEVSEStatus) and the name in the XSD (AC_EVSEStatus)
*/
JAXBElement jaxbEVSEStatus = new JAXBElement(new QName("urn:iso:15118:2:2013:MsgDataTypes", "AC_EVSEStatus"),
ACEVSEStatusType.class,
getCommSessionContext().getACEvseController().getACEVSEStatus(EVSENotificationType.NONE));
meteringReceiptRes.setEVSEStatus(jaxbEVSEStatus);
} else if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("DC")) {
/*
* The MiscUtils method getJAXBElement() cannot be used here because of the difference in the
* class name (DCEVSEStatus) and the name in the XSD (DC_EVSEStatus)
*/
JAXBElement jaxbACEVSEStatus = new JAXBElement(new QName("urn:iso:15118:2:2013:MsgDataTypes", "DC_EVSEStatus"),
DCEVSEStatusType.class,
getCommSessionContext().getDCEvseController().getDCEVSEStatus(EVSENotificationType.NONE));
meteringReceiptRes.setEVSEStatus(jaxbACEVSEStatus);
} else {
return new TerminateSession("RequestedEnergyTransferMode '" + getCommSessionContext().getRequestedEnergyTransferMode().toString() +
"is neither of type AC nor DC");
}
setEVSEStatus(meteringReceiptRes);
((ForkState) getCommSessionContext().getStates().get(V2GMessages.FORK))
.getAllowedRequests().add(V2GMessages.POWER_DELIVERY_REQ);
@ -83,8 +60,11 @@ public class WaitForMeteringReceiptReq extends ServerState {
return getSendMessage(meteringReceiptRes, V2GMessages.FORK);
} else {
getLogger().error("Response code '" + meteringReceiptRes.getResponseCode() + "' will be sent");
setMandatoryFieldsForFailedRes();
}
}
} else {
setMandatoryFieldsForFailedRes();
}
return getSendMessage(meteringReceiptRes, V2GMessages.NONE);
}
@ -104,11 +84,12 @@ public class WaitForMeteringReceiptReq extends ServerState {
// Verify signature
HashMap<String, byte[]> verifyXMLSigRefElements = new HashMap<String, byte[]>();
verifyXMLSigRefElements.put(meteringReceiptReq.getId(), SecurityUtils.generateDigest(meteringReceiptReq, false));
ECPublicKey ecPublicKey = (ECPublicKey) SecurityUtils.getCertificate(
getCommSessionContext().getContractSignatureCertChain().getCertificate())
.getPublicKey();
if (!SecurityUtils.verifySignature(signature, verifyXMLSigRefElements, ecPublicKey)) {
verifyXMLSigRefElements.put(meteringReceiptReq.getId(), SecurityUtils.generateDigest(meteringReceiptReq));
if (!SecurityUtils.verifySignature(
signature,
verifyXMLSigRefElements,
getCommSessionContext().getContractSignatureCertChain().getCertificate())) {
meteringReceiptRes.setResponseCode(ResponseCodeType.FAILED_METERING_SIGNATURE_NOT_VALID);
return false;
}
@ -135,4 +116,36 @@ public class WaitForMeteringReceiptReq extends ServerState {
else return true;
}
}
private void setEVSEStatus(MeteringReceiptResType meteringReceiptRes) {
if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("AC")) {
/*
* The MiscUtils method getJAXBElement() cannot be used here because of the difference in the
* class name (ACEVSEStatus) and the name in the XSD (AC_EVSEStatus)
*/
JAXBElement jaxbEVSEStatus = new JAXBElement(new QName("urn:iso:15118:2:2013:MsgDataTypes", "AC_EVSEStatus"),
ACEVSEStatusType.class,
getCommSessionContext().getACEvseController().getACEVSEStatus(EVSENotificationType.NONE));
meteringReceiptRes.setEVSEStatus(jaxbEVSEStatus);
} else if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("DC")) {
/*
* The MiscUtils method getJAXBElement() cannot be used here because of the difference in the
* class name (DCEVSEStatus) and the name in the XSD (DC_EVSEStatus)
*/
JAXBElement jaxbACEVSEStatus = new JAXBElement(new QName("urn:iso:15118:2:2013:MsgDataTypes", "DC_EVSEStatus"),
DCEVSEStatusType.class,
getCommSessionContext().getDCEvseController().getDCEVSEStatus(EVSENotificationType.NONE));
meteringReceiptRes.setEVSEStatus(jaxbACEVSEStatus);
} else {
getLogger().warn("RequestedEnergyTransferMode '" + getCommSessionContext().getRequestedEnergyTransferMode().toString() +
"is neither of type AC nor DC");
}
}
@Override
protected void setMandatoryFieldsForFailedRes() {
setEVSEStatus(meteringReceiptRes);
}
}

View File

@ -10,10 +10,13 @@
*******************************************************************************/
package org.eclipse.risev2g.secc.states;
import java.security.cert.X509Certificate;
import org.eclipse.risev2g.secc.session.V2GCommunicationSessionSECC;
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.utils.ByteUtils;
import org.eclipse.risev2g.shared.utils.SecurityUtils;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PaymentDetailsReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PaymentDetailsResType;
@ -46,8 +49,11 @@ public class WaitForPaymentDetailsReq extends ServerState {
paymentDetailsRes.setGenChallenge(genChallenge);
} else {
getLogger().error("Response code '" + paymentDetailsRes.getResponseCode() + "' will be sent");
setMandatoryFieldsForFailedRes();
}
}
} else {
setMandatoryFieldsForFailedRes();
}
return getSendMessage(paymentDetailsRes,
(paymentDetailsRes.getResponseCode().toString().startsWith("OK") ?
@ -89,4 +95,11 @@ public class WaitForPaymentDetailsReq extends ServerState {
return true;
}
@Override
protected void setMandatoryFieldsForFailedRes() {
paymentDetailsRes.setEVSETimeStamp(0L);
paymentDetailsRes.setGenChallenge(new byte[1]);
}
}

View File

@ -39,6 +39,7 @@ public class WaitForPaymentServiceSelectionReq extends ServerState {
getLogger().info("Payment option " + paymentServiceSelectionReq.getSelectedPaymentOption().toString() +
" has been chosen by EVCC");
getCommSessionContext().setSelectedPaymentOption(paymentServiceSelectionReq.getSelectedPaymentOption());
if (isResponseCodeOK(paymentServiceSelectionReq)) {
// see [V2G2-551]
@ -56,8 +57,11 @@ public class WaitForPaymentServiceSelectionReq extends ServerState {
}
} else {
getLogger().error("Response code '" + paymentServiceSelectionRes.getResponseCode() + "' will be sent");
setMandatoryFieldsForFailedRes();
}
}
} else {
setMandatoryFieldsForFailedRes();
}
return getSendMessage(paymentServiceSelectionRes, V2GMessages.NONE);
}
@ -108,4 +112,10 @@ public class WaitForPaymentServiceSelectionReq extends ServerState {
return true;
}
@Override
protected void setMandatoryFieldsForFailedRes() {
// No other mandatory fields to be set besides response code
}
}

View File

@ -16,7 +16,6 @@ import javax.xml.namespace.QName;
import org.eclipse.risev2g.secc.session.V2GCommunicationSessionSECC;
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.v2gMessages.msgDef.ACEVSEStatusType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ChargeProgressType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ChargingProfileType;
@ -54,29 +53,7 @@ public class WaitForPowerDeliveryReq extends ServerState {
// TODO regard [V2G2-866]
if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("AC")) {
/*
* The MiscUtils method getJAXBElement() cannot be used here because of the difference in the
* class name (ACEVSEStatus) and the name in the XSD (AC_EVSEStatus)
*/
JAXBElement jaxbEVSEStatus = new JAXBElement(new QName("urn:iso:15118:2:2013:MsgDataTypes", "AC_EVSEStatus"),
ACEVSEStatusType.class,
getCommSessionContext().getACEvseController().getACEVSEStatus(EVSENotificationType.NONE));
powerDeliveryRes.setEVSEStatus(jaxbEVSEStatus);
} else if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("DC")) {
/*
* The MiscUtils method getJAXBElement() cannot be used here because of the difference in the
* class name (DCEVSEStatus) and the name in the XSD (DC_EVSEStatus)
*/
JAXBElement jaxbACEVSEStatus = new JAXBElement(new QName("urn:iso:15118:2:2013:MsgDataTypes", "DC_EVSEStatus"),
DCEVSEStatusType.class,
getCommSessionContext().getDCEvseController().getDCEVSEStatus(EVSENotificationType.NONE));
powerDeliveryRes.setEVSEStatus(jaxbACEVSEStatus);
} else {
return new TerminateSession("RequestedEnergyTransferMode '" + getCommSessionContext().getRequestedEnergyTransferMode().toString() +
"is neither of type AC nor DC");
}
setEVSEStatus(powerDeliveryRes);
if (powerDeliveryReq.getChargeProgress().equals(ChargeProgressType.START)) {
if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("AC"))
@ -99,8 +76,11 @@ public class WaitForPowerDeliveryReq extends ServerState {
}
} else {
getLogger().error("Response code '" + powerDeliveryRes.getResponseCode() + "' will be sent");
setMandatoryFieldsForFailedRes();
}
}
} else {
setMandatoryFieldsForFailedRes();
}
return getSendMessage(powerDeliveryRes, V2GMessages.NONE);
}
@ -155,6 +135,32 @@ public class WaitForPowerDeliveryReq extends ServerState {
}
private void setEVSEStatus(PowerDeliveryResType powerDeliveryRes) {
if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("AC")) {
/*
* The MiscUtils method getJAXBElement() cannot be used here because of the difference in the
* class name (ACEVSEStatus) and the name in the XSD (AC_EVSEStatus)
*/
JAXBElement jaxbEVSEStatus = new JAXBElement(new QName("urn:iso:15118:2:2013:MsgDataTypes", "AC_EVSEStatus"),
ACEVSEStatusType.class,
getCommSessionContext().getACEvseController().getACEVSEStatus(EVSENotificationType.NONE));
powerDeliveryRes.setEVSEStatus(jaxbEVSEStatus);
} else if (getCommSessionContext().getRequestedEnergyTransferMode().toString().startsWith("DC")) {
/*
* The MiscUtils method getJAXBElement() cannot be used here because of the difference in the
* class name (DCEVSEStatus) and the name in the XSD (DC_EVSEStatus)
*/
JAXBElement jaxbACEVSEStatus = new JAXBElement(new QName("urn:iso:15118:2:2013:MsgDataTypes", "DC_EVSEStatus"),
DCEVSEStatusType.class,
getCommSessionContext().getDCEvseController().getDCEVSEStatus(EVSENotificationType.NONE));
powerDeliveryRes.setEVSEStatus(jaxbACEVSEStatus);
} else {
getLogger().warn("RequestedEnergyTransferMode '" + getCommSessionContext().getRequestedEnergyTransferMode().toString() +
"is neither of type AC nor DC");
}
}
private SAScheduleTupleType getChosenSASCheduleTuple(short chosenSAScheduleTupleID) {
for (SAScheduleTupleType saSchedule : getCommSessionContext().getSaSchedules().getSAScheduleTuple()) {
if (saSchedule.getSAScheduleTupleID() == chosenSAScheduleTupleID) return saSchedule;
@ -170,4 +176,10 @@ public class WaitForPowerDeliveryReq extends ServerState {
return true;
}
@Override
protected void setMandatoryFieldsForFailedRes() {
setEVSEStatus(powerDeliveryRes);
}
}

View File

@ -49,11 +49,22 @@ public class WaitForPreChargeReq extends ServerState {
.getAllowedRequests().add(V2GMessages.PRE_CHARGE_REQ);
((ForkState) getCommSessionContext().getStates().get(V2GMessages.FORK))
.getAllowedRequests().add(V2GMessages.POWER_DELIVERY_REQ);
}
} else {
setMandatoryFieldsForFailedRes();
}
return getSendMessage(preChargeRes,
(preChargeRes.getResponseCode().toString().startsWith("OK") ?
V2GMessages.FORK : V2GMessages.NONE)
);
}
@Override
protected void setMandatoryFieldsForFailedRes() {
IDCEVSEController evseController = (IDCEVSEController) getCommSessionContext().getDCEvseController();
preChargeRes.setDCEVSEStatus(evseController.getDCEVSEStatus(EVSENotificationType.NONE));
preChargeRes.setEVSEPresentVoltage(evseController.getPresentVoltage());
}
}

View File

@ -45,7 +45,7 @@ public class WaitForServiceDetailReq extends ServerState {
serviceParameterList.getParameterSet().add(getCertificateInstallationParameters());
serviceParameterList.getParameterSet().add(getCertificateUpdateParameters());
} else if (serviceDetailReq.getServiceID() == 3) {
// Comment out internet access service which will not be available
// Comment out Internet access service which will not be available
serviceParameterList.getParameterSet().add(getInternetAccessFTPPort20Parameters());
serviceParameterList.getParameterSet().add(getInternetAccessFTPPort21Parameters());
serviceParameterList.getParameterSet().add(getInternetAccessHTTPParameters());
@ -69,8 +69,11 @@ public class WaitForServiceDetailReq extends ServerState {
return getSendMessage(serviceDetailRes, V2GMessages.FORK);
} else {
getLogger().error("Response code '" + serviceDetailRes.getResponseCode() + "' will be sent");
setMandatoryFieldsForFailedRes();
}
}
} else {
setMandatoryFieldsForFailedRes();
}
return getSendMessage(serviceDetailRes, V2GMessages.NONE);
}
@ -91,7 +94,7 @@ public class WaitForServiceDetailReq extends ServerState {
ParameterSetType parameterSet = new ParameterSetType();
ParameterType certInstallation = new ParameterType();
certInstallation.setName("Certificate Installation");
certInstallation.setName("Service");
certInstallation.setStringValue("Installation");
parameterSet.getParameter().add(certInstallation);
@ -105,7 +108,7 @@ public class WaitForServiceDetailReq extends ServerState {
ParameterSetType parameterSet = new ParameterSetType();
ParameterType certUpdate = new ParameterType();
certUpdate.setName("Certificate Update");
certUpdate.setName("Service");
certUpdate.setStringValue("Update");
parameterSet.getParameter().add(certUpdate);
@ -119,7 +122,7 @@ public class WaitForServiceDetailReq extends ServerState {
ParameterSetType parameterSet = new ParameterSetType();
ParameterType ftpPort20 = new ParameterType();
ftpPort20.setName("FTP port 20");
ftpPort20.setName("FTP20");
ftpPort20.setStringValue("ftp");
ftpPort20.setIntValue(20);
@ -134,7 +137,7 @@ public class WaitForServiceDetailReq extends ServerState {
ParameterSetType parameterSet = new ParameterSetType();
ParameterType ftpPort21 = new ParameterType();
ftpPort21.setName("FTP port 21");
ftpPort21.setName("FTP21");
ftpPort21.setStringValue("ftp");
ftpPort21.setIntValue(21);
@ -174,4 +177,10 @@ public class WaitForServiceDetailReq extends ServerState {
return parameterSet;
}
@Override
protected void setMandatoryFieldsForFailedRes() {
serviceDetailRes.setServiceID(1);
}
}

View File

@ -60,7 +60,9 @@ public class WaitForServiceDiscoveryReq extends ServerState {
.getAllowedRequests().add(V2GMessages.SERVICE_DETAIL_REQ);
((ForkState) getCommSessionContext().getStates().get(V2GMessages.FORK))
.getAllowedRequests().add(V2GMessages.PAYMENT_SERVICE_SELECTION_REQ);
}
} else {
setMandatoryFieldsForFailedRes();
}
return getSendMessage(serviceDiscoveryRes,
(serviceDiscoveryRes.getResponseCode().toString().startsWith("OK") ?
@ -83,13 +85,13 @@ public class WaitForServiceDiscoveryReq extends ServerState {
* 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)");
chargeService.setServiceName("AC_DC_Charging");
/*
* 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.setServiceScope("chargingServiceScope");
chargeService.setFreeService(false); // it is supposed that charging is by default not for free
@ -100,7 +102,10 @@ public class WaitForServiceDiscoveryReq extends ServerState {
private ServiceListType getServiceList(ServiceCategoryType serviceCategoryFilter, String serviceScopeFilter) {
ServiceListType serviceList = new ServiceListType();
// Currently no filter based on service scope is applied since its string value is not standardised somehow
if (serviceCategoryFilter != null)
getLogger().debug("EVCC filters offered services by category: " + serviceScopeFilter.toString());
// Currently no filter based on service scope is applied since its string value is not standardized somehow
if (getCommSessionContext().isTlsConnection() && (
(serviceCategoryFilter != null && serviceCategoryFilter.equals(ServiceCategoryType.CONTRACT_CERTIFICATE)) ||
serviceCategoryFilter == null)) {
@ -118,12 +123,24 @@ public class WaitForServiceDiscoveryReq extends ServerState {
private ServiceType getCertificateService() {
ServiceType certificateService = new ServiceType();
certificateService.setFreeService(false); // it is supposed that certificate installation is by default not for free
certificateService.setFreeService(true);
certificateService.setServiceCategory(ServiceCategoryType.CONTRACT_CERTIFICATE);
certificateService.setServiceID(2); // according to Table 105 ISO/IEC 15118-2
certificateService.setServiceName("Contrac certificate installation/update"); // optional value
certificateService.setServiceScope(""); // optional value
certificateService.setServiceName("Certificate"); // 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!
*/
certificateService.setServiceScope("certificateServiceScope");
return certificateService;
}
@Override
protected void setMandatoryFieldsForFailedRes() {
serviceDiscoveryRes.setChargeService(getChargeService());
serviceDiscoveryRes.setPaymentOptionList(getCommSessionContext().getPaymentOptions());
}
}

View File

@ -32,6 +32,8 @@ public class WaitForSessionSetupReq extends ServerState {
// Unix time stamp is needed (seconds instead of milliseconds)
sessionSetupRes.setEVSETimeStamp(System.currentTimeMillis() / 1000L);
} else {
setMandatoryFieldsForFailedRes();
}
return getSendMessage(sessionSetupRes,
@ -40,4 +42,10 @@ public class WaitForSessionSetupReq extends ServerState {
);
}
@Override
protected void setMandatoryFieldsForFailedRes() {
sessionSetupRes.setEVSEID(getCommSessionContext().getEvseController().getEvseID());
}
}

View File

@ -29,9 +29,17 @@ public class WaitForSessionStopReq extends ServerState {
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, SessionStopReqType.class, sessionStopRes)) {
getCommSessionContext().setStopV2GCommunicationSession(true);
}
} else {
setMandatoryFieldsForFailedRes();
}
return getSendMessage(sessionStopRes, V2GMessages.NONE);
}
@Override
protected void setMandatoryFieldsForFailedRes() {
// No other fields need to be set besides response code
}
}

View File

@ -24,6 +24,7 @@ import org.eclipse.risev2g.shared.v2gMessages.appProtocol.AppProtocolType;
import org.eclipse.risev2g.shared.v2gMessages.appProtocol.ResponseCodeType;
import org.eclipse.risev2g.shared.v2gMessages.appProtocol.SupportedAppProtocolReq;
import org.eclipse.risev2g.shared.v2gMessages.appProtocol.SupportedAppProtocolRes;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.BodyBaseType;
public class WaitForSupportedAppProtocolReq extends ServerState {
@ -122,4 +123,10 @@ public class WaitForSupportedAppProtocolReq extends ServerState {
return supportedAppProtocols;
}
@Override
protected void setMandatoryFieldsForFailedRes() {
// No additional mandatory fields besides response code
}
}

View File

@ -46,11 +46,22 @@ public class WaitForWeldingDetectionReq extends ServerState {
.getAllowedRequests().add(V2GMessages.WELDING_DETECTION_REQ);
((ForkState) getCommSessionContext().getStates().get(V2GMessages.FORK))
.getAllowedRequests().add(V2GMessages.SESSION_STOP_REQ);
}
} else {
setMandatoryFieldsForFailedRes();
}
return getSendMessage(weldingDetectionRes,
(weldingDetectionRes.getResponseCode().toString().startsWith("OK") ?
V2GMessages.FORK : V2GMessages.NONE)
);
}
@Override
protected void setMandatoryFieldsForFailedRes() {
IDCEVSEController evseController = (IDCEVSEController) getCommSessionContext().getDCEvseController();
weldingDetectionRes.setDCEVSEStatus(evseController.getDCEVSEStatus(EVSENotificationType.NONE));
weldingDetectionRes.setEVSEPresentVoltage(evseController.getPresentVoltage());
}
}

View File

@ -100,7 +100,7 @@ public final class TLSServer extends StatefulTransportLayerServer {
ConnectionHandler connectionHandler = new ConnectionHandler(tlsClientSocket);
// Notify the V2GCommunicationSessionHandlerSECC about a newly connected TCP client Socket
// Notify the V2GCommunicationSessionHandlerSECC about a newly connected TLS client socket
setChanged();
notifyObservers(connectionHandler);
}
@ -122,7 +122,7 @@ public final class TLSServer extends StatefulTransportLayerServer {
getLogger().error("Error occurred while trying to close TLSServerSocket (IOException)", e);
}
getLogger().debug("TCP server stopped");
getLogger().debug("TLS server stopped");
}
public SSLSocket getTlsClientSocket() {

View File

@ -58,4 +58,28 @@
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.sonatype.haven.HavenCli</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -61,6 +61,7 @@ public enum GlobalValues {
// Schema information
V2G_CI_MSG_DEF_NAMESPACE("urn:iso:15118:2:2013:MsgDef"),
V2G_CI_MSG_BODY_NAMESPACE("urn:iso:15118:2:2013:MsgBody"),
V2G_CI_MSG_DATATYPES_NAMESPACE("urn:iso:15118:2:2013:MsgDataTypes"),
V2G_CI_XMLDSIG_NAMESPACE("http://www.w3.org/2000/09/xmldsig#"),
SCHEMA_PATH_APP_PROTOCOL("/schemas/V2G_CI_AppProtocol.xsd"),
SCHEMA_PATH_MSG_DEF("/schemas/V2G_CI_MsgDef.xsd"),
@ -136,33 +137,35 @@ public enum GlobalValues {
case SECC_CONFIG_PROPERTIES_PATH:
return stringValue;
case PASSPHRASE_FOR_CERTIFICATES_AND_KEYS:
return "123456";
return stringValue;
case SDP_MULTICAST_ADDRESS:
return "FF02::1";
return stringValue;
case V2G_UDP_SDP_SERVER_PORT:
return "15118";
return stringValue;
case V2GTP_HEADER_MAX_PAYLOAD_LENGTH:
return "4294967295 bytes";
case V2GTP_VERSION_1_IS:
return "version 1 (IS compliant)";
case V2G_CI_MSG_DEF_NAMESPACE:
return "urn:iso:15118:2:2013:MsgDef";
return stringValue;
case V2G_CI_MSG_BODY_NAMESPACE:
return "urn:iso:15118:2:2013:MsgBody";
return stringValue;
case V2G_CI_MSG_DATATYPES_NAMESPACE:
return stringValue;
case V2G_CI_XMLDSIG_NAMESPACE:
return "http://www.w3.org/2000/09/xmldsig#";
return stringValue;
case SCHEMA_PATH_APP_PROTOCOL:
return "/schemas/V2G_CI_AppProtocol.xsd";
return stringValue;
case SCHEMA_PATH_MSG_DEF:
return "/schemas/V2G_CI_MsgDef.xsd";
return stringValue;
case SCHEMA_PATH_XMLDSIG:
return "/schemas/xmldsig-core-schema.xsd";
return stringValue;
case SCHEMA_PATH_MSG_BODY:
return "/schemas/V2G_CI_MsgBody.xsd";
return stringValue;
case SCHEMA_PATH_MSG_HEADER:
return "/schemas/V2G_CI_MsgHeader.xsd";
return stringValue;
case SCHEMA_PATH_MSG_DATA_TYPES:
return "/schemas/V2G_CI_MsgDataTypes.xsd";
return stringValue;
case V2G_SECURITY_WITH_TLS:
return "TLS enabled";
case V2G_SECURITY_WITHOUT_TLS:
@ -178,19 +181,19 @@ public enum GlobalValues {
case V2GTP_PAYLOAD_TYPE_SDP_RESPONSE_MESSAGE:
return "SDP response message";
case ALIAS_CONTRACT_CERTIFICATE:
return "contract_cert";
return stringValue;
case ALIAS_OEM_PROV_CERTIFICATE:
return "oem_prov_cert";
return stringValue;
case EVCC_KEYSTORE_FILEPATH:
return "./evccKeystore.jks";
return stringValue;
case EVCC_TRUSTSTORE_FILEPATH:
return "./evccTruststore.jks";
return stringValue;
case SECC_KEYSTORE_FILEPATH:
return "./seccKeystore.jks";
return stringValue;
case SECC_TRUSTSTORE_FILEPATH:
return "./seccTruststore.jks";
return stringValue;
case CERTIFICATE_EXPIRES_SOON_PERIOD:
return "21 days";
return shortValue + " days";
default: return "Invalid GlobalValue type";
}
}

View File

@ -27,14 +27,11 @@ import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamResult;
import org.eclipse.risev2g.shared.enumerations.GlobalValues;
import org.eclipse.risev2g.shared.utils.ByteUtils;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;
import com.siemens.ct.exi.EXIFactory;
import com.siemens.ct.exi.EncodingOptions;
import com.siemens.ct.exi.GrammarFactory;
import com.siemens.ct.exi.api.sax.EXIResult;
import com.siemens.ct.exi.api.sax.EXISource;
@ -62,8 +59,6 @@ public final class EXIficientCodec extends ExiCodec {
super();
setExiFactory(DefaultEXIFactory.newInstance());
getExiFactory().setValuePartitionCapacity(0);
setFragment(false); // needs to be set to true when encoding signatures
setGrammarFactory(GrammarFactory.newInstance());
/*
@ -82,6 +77,11 @@ public final class EXIficientCodec extends ExiCodec {
} catch (EXIException e) {
getLogger().error("Error occurred while trying to initialize EXIficientCodec (EXIException)!", e);
}
// Non-default settings to fulfill requirements [V2G2-099] and [V2G2-600]
getExiFactory().setValuePartitionCapacity(0);
getExiFactory().setMaximumNumberOfBuiltInElementGrammars(0);
getExiFactory().setMaximumNumberOfBuiltInProductions(0);
}
public static EXIficientCodec getInstance() {
@ -107,12 +107,10 @@ public final class EXIficientCodec extends ExiCodec {
}
InputStream inStream = marshalToInputStream(jaxbObject);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos = ((ByteArrayOutputStream) encode(inStream, grammar));
// If needed for debugging
// getLogger().debug("Encoded EXI byte stream to be sent: " + ByteUtils.toHexString(baos.toByteArray()));
return baos.toByteArray();
}
@ -141,9 +139,6 @@ public final class EXIficientCodec extends ExiCodec {
@Override
public synchronized Object decodeEXI(byte[] exiEncodedMessage, boolean supportedAppProtocolHandshake) {
// If needed for debugging
// getLogger().debug("Decoded incoming EXI stream: " + ByteUtils.toHexString(exiEncodedMessage));
ByteArrayInputStream bais = new ByteArrayInputStream(exiEncodedMessage);
setDecodedExi(decode(bais, supportedAppProtocolHandshake));

View File

@ -15,17 +15,22 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
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;
import javax.xml.bind.ValidationEvent;
import javax.xml.bind.ValidationEventHandler;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.eclipse.risev2g.shared.enumerations.GlobalValues;
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.SignedInfoType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public abstract class ExiCodec {
@ -74,29 +79,11 @@ public abstract class ExiCodec {
try {
if (getInStream() != null) getInStream().reset();
getMarshaller().marshal(jaxbObject, baos);
setInStream(new ByteArrayInputStream(baos.toByteArray()));
baos.close();
if (isXMLRepresentation()) {
// For debugging purposes, you can view the XML representation of marshalled messages
StringWriter sw = new StringWriter();
String className = "";
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();
}
if (isXMLRepresentation()) showXMLRepresentationOfMessage(jaxbObject);
return getInStream();
} catch (JAXBException | IOException e) {
getLogger().error(e.getClass().getSimpleName() + " occurred while trying to marshal to InputStream from JAXBElement", e);
@ -109,7 +96,10 @@ public abstract class ExiCodec {
try {
if (getInStream() != null) getInStream().reset();
setInStream(new ByteArrayInputStream(decodedExiString.getBytes()));
return getUnmarshaller().unmarshal(getInStream());
Object unmarhalledObject = getUnmarshaller().unmarshal(getInStream());
if (isXMLRepresentation()) showXMLRepresentationOfMessage(unmarhalledObject);
return unmarhalledObject;
} catch (IOException | JAXBException | RuntimeException e) {
getLogger().error(e.getClass().getSimpleName() + " occurred while trying to unmarshall decoded message", e);
return null;
@ -117,6 +107,62 @@ public abstract class ExiCodec {
}
/**
* Shows the XML representation of a marshalled or unmarshalled message object. This is useful for debugging
* purposes.
*
* @param message The (un)marshalled message object
*/
@SuppressWarnings("rawtypes")
public void showXMLRepresentationOfMessage(Object message) {
StringWriter sw = new StringWriter();
String className = "";
if (message instanceof V2GMessage) {
className = ((V2GMessage) message).getBody().getBodyElement().getName().getLocalPart();
} else if (message instanceof JAXBElement) {
className = ((JAXBElement) message).getName().getLocalPart();
} else if (message instanceof SupportedAppProtocolReq) {
className = "SupportedAppProtocolReq";
} else if (message instanceof SupportedAppProtocolRes) {
className = "SupportedAppProtocolRes";
} else {
className = "marshalled JAXBElement";
}
try {
getMarshaller().marshal(message, sw);
getLogger().debug("XML representation of " + className + ":\n" + sw.toString());
} catch (JAXBException e) {
getLogger().error(e.getClass().getSimpleName() + " occurred while trying to show XML representation of " + className, e);
}
}
/**
* Provides the EXI encoding of the header's SignedInfo element. The resulting byte array can then be used to
* verify a signature.
*
* @param signedInfo The SignedInfo element of the V2GMessage header
* @return The EXI encoding of the SignedInfo element given as a byte array
*/
public byte[] getExiEncodedSignedInfo(SignedInfoType signedInfo) {
// The schema-informed fragment grammar option needs to be used for EXI encodings in the header's signature
setFragment(true);
// The SignedInfo element must be encoded
byte[] encodedSignedInfo = encodeEXI(
MiscUtils.getJaxbElement(signedInfo),
GlobalValues.SCHEMA_PATH_XMLDSIG.toString()
);
// Do not use the schema-informed fragment grammar option for other EXI encodings (message bodies)
setFragment(false);
return encodedSignedInfo;
}
public abstract byte[] encodeEXI(Object jaxbXML, String xsdSchemaPath);
public abstract Object decodeEXI(byte[] exiEncodedMessage, boolean supportedAppProtocolHandshake);

View File

@ -46,9 +46,14 @@ public final class OpenEXICodec extends ExiCodec {
private GrammarCache grammarCache;
private EXISchemaFactory exiSchemaFactory;
private InputStream schemaMsgDefIS;
private InputStream schemaMsgBodyIS;
private InputStream schemaMsgDataTypesIS;
private InputStream schemaXMLDSigIS;
private InputStream schemaAppProtocolIS;
private EXISchema exiSchemaAppProtocol;
private EXISchema exiSchemaMsgDef;
private EXISchema exiSchemaMsgBody;
private EXISchema exiSchemaMsgDataTypes;
private EXISchema exiSchemaXMLDSig;
private short options;
private SAXTransformerFactory saxTransformerFactory;
@ -89,6 +94,9 @@ public final class OpenEXICodec extends ExiCodec {
setSchemaAppProtocolIS(getClass().getResourceAsStream(GlobalValues.SCHEMA_PATH_APP_PROTOCOL.toString()));
setSchemaMsgDefIS(getClass().getResourceAsStream(GlobalValues.SCHEMA_PATH_MSG_DEF.toString()));
setSchemaMsgBodyIS(getClass().getResourceAsStream(GlobalValues.SCHEMA_PATH_MSG_BODY.toString()));
setSchemaMsgDataTypesIS(getClass().getResourceAsStream(GlobalValues.SCHEMA_PATH_MSG_DATA_TYPES.toString()));
setSchemaXMLDSigIS(getClass().getResourceAsStream(GlobalValues.SCHEMA_PATH_XMLDSIG.toString()));
/*
* It is currently a problem with OpenExi to resolve the XSDs which are imported in V2G_CI_MsgDef.xsd.
@ -99,6 +107,7 @@ public final class OpenEXICodec extends ExiCodec {
try {
setExiSchemaAppProtocol(getExiSchemaFactory().compile(new InputSource(getSchemaAppProtocolIS())));
setExiSchemaMsgDef(new EXISchemaReader().parse(getClass().getResourceAsStream("/schemas/V2G_CI_MsgDef.exig")));
setExiSchemaXMLDSig(getExiSchemaFactory().compile(new InputSource(getSchemaXMLDSigIS())));
} catch (IOException | EXISchemaFactoryException | EXIOptionsException e) {
getLogger().error(e.getClass().getSimpleName() + " occurred while trying to set EXI schema", e);
}
@ -297,4 +306,44 @@ public final class OpenEXICodec extends ExiCodec {
public void setFragment(boolean useFragmentGrammar) {
getTransmogrifier().setFragment(useFragmentGrammar);
}
public EXISchema getExiSchemaMsgBody() {
return exiSchemaMsgBody;
}
public void setExiSchemaMsgBody(EXISchema exiSchemaMsgBody) {
this.exiSchemaMsgBody = exiSchemaMsgBody;
}
public EXISchema getExiSchemaMsgDataTypes() {
return exiSchemaMsgDataTypes;
}
public void setExiSchemaMsgDataTypes(EXISchema exiSchemaMsgDataTypes) {
this.exiSchemaMsgDataTypes = exiSchemaMsgDataTypes;
}
public InputStream getSchemaMsgBodyIS() {
return schemaMsgBodyIS;
}
public void setSchemaMsgBodyIS(InputStream schemaMsgBodyIS) {
this.schemaMsgBodyIS = schemaMsgBodyIS;
}
public InputStream getSchemaMsgDataTypesIS() {
return schemaMsgDataTypesIS;
}
public void setSchemaMsgDataTypesIS(InputStream schemaMsgDataTypesIS) {
this.schemaMsgDataTypesIS = schemaMsgDataTypesIS;
}
public InputStream getSchemaXMLDSigIS() {
return schemaXMLDSigIS;
}
public void setSchemaXMLDSigIS(InputStream schemaXMLDSigIS) {
this.schemaXMLDSigIS = schemaXMLDSigIS;
}
}

View File

@ -171,8 +171,10 @@ public class MessageHandler {
if (xmlSignatureRefElements != null && xmlSignatureRefElements.size() != 0) {
SignedInfoType signedInfo = SecurityUtils.getSignedInfo(xmlSignatureRefElements);
byte[] digest = SecurityUtils.generateDigest(signedInfo, true);
byte[] signature = SecurityUtils.signSignatureInfo(digest, signaturePrivateKey);
byte[] signature = SecurityUtils.signSignedInfoElement(
getExiCodec().getExiEncodedSignedInfo(signedInfo),
signaturePrivateKey
);
SignatureValueType signatureValue = new SignatureValueType();
signatureValue.setValue(signature);

View File

@ -52,6 +52,7 @@ public class V2GTPMessage {
if (byteArray != null && byteArray.length >= 8) {
setProtocolVersion(Arrays.copyOfRange(byteArray, 0, 1)[0]);
setPayloadType(Arrays.copyOfRange(byteArray, 2, 4));
// TODO make sure the byteArray is not too long to not generate a Java heap space OutOfMemoryError
setPayload(Arrays.copyOfRange(byteArray, 8, byteArray.length));
} else {
getLogger().error("Received byte array does not match a V2GTPMessage");

View File

@ -218,4 +218,14 @@ public final class ByteUtils {
return null;
}
}
public static byte[] intToFourBytes(int i) {
byte[] res = new byte[4];
res[0] = (byte) (i >>> 24);
res[1] = (byte) ((i >>> 16) & 0xFF);
res[2] = (byte) ((i >>> 8) & 0xFF);
res[3] = (byte) (i & 0xFF);
return res;
}
}

View File

@ -28,6 +28,7 @@ import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.eclipse.risev2g.shared.enumerations.GlobalValues;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.BodyBaseType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.EnergyTransferModeType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PaymentOptionType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.SignedInfoType;
@ -216,6 +217,10 @@ public final class MiscUtils {
if (Boolean.parseBoolean(propertyValue)) returnValue = true;
else returnValue = false;
break;
case "SignatureVerificationLog": // EV + EVSE property
if (Boolean.parseBoolean(propertyValue)) returnValue = true;
else returnValue = false;
break;
default:
getLogger().error("No property with name '" + propertyName + "' found");
}
@ -273,7 +278,7 @@ public final class MiscUtils {
if (messageOrField instanceof SignedInfoType) namespace = GlobalValues.V2G_CI_XMLDSIG_NAMESPACE.toString();
else namespace = GlobalValues.V2G_CI_MSG_BODY_NAMESPACE.toString();
return new JAXBElement(new QName(namespace, messageName),
messageOrField.getClass(),
messageOrField);

View File

@ -12,16 +12,19 @@ package org.eclipse.risev2g.shared.utils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyManagementException;
import java.security.KeyPair;
@ -49,24 +52,28 @@ import java.security.spec.ECGenParameterSpec;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPrivateKeySpec;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.InvalidParameterSpecException;
import java.security.spec.X509EncodedKeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
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;
import javax.crypto.EncryptedPrivateKeyInfo;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyAgreement;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import javax.naming.InvalidNameException;
import javax.naming.ldap.LdapName;
@ -78,6 +85,7 @@ import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.security.auth.x500.X500Principal;
import javax.xml.bind.JAXBElement;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.eclipse.risev2g.shared.enumerations.GlobalValues;
@ -98,10 +106,16 @@ import org.eclipse.risev2g.shared.v2gMessages.msgDef.TransformType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.TransformsType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.X509IssuerSerialType;
public final class SecurityUtils {
import java.util.Base64;
public final class SecurityUtils {
/*
* Add VM (virtual machine) argument "-Djavax.net.debug=ssl" if you want more detailed debugging output
*/
static Logger logger = LogManager.getLogger(SecurityUtils.class.getSimpleName());
static ExiCodec exiCodec;
static boolean showSignatureVerificationLog = ((boolean) MiscUtils.getPropertyValue("SignatureVerificationLog"));
public static enum ContractCertificateStatus {
UPDATE_NEEDED,
@ -345,10 +359,13 @@ public final class SecurityUtils {
return true;
} catch (InvalidKeyException | CertificateException | NoSuchAlgorithmException |
NoSuchProviderException | SignatureException e) {
getLogger().warn("Signature verification of certificate having distinguished name '" +
subject.getName() + "' with certificate having distinguished name (the issuer) '" +
issuerSubject.getName() + "' failed. Expected issuer has distinguished name '" +
expectedIssuerSubject.getName() + "' (" + e.getClass().getSimpleName() + ")", e);
getLogger().warn("\n"
+ "\tSignature verification of certificate having distinguished name \n"
+ "\t'" + subject.getName() + "'\n"
+ "\twith certificate having distinguished name (the issuer) \n"
+ "\t'" + issuerSubject.getName() + "'\n"
+ "\tfailed. Expected issuer has distinguished name \n"
+ "\t'" + expectedIssuerSubject.getName() + "' (" + e.getClass().getSimpleName() + ")", e);
}
return false;
@ -372,7 +389,7 @@ public final class SecurityUtils {
if (leafCertificate != null) {
SubCertificatesType subCertificates = certChain.getSubCertificates();
if (subCertificates != null) {
// Sub certificates must be in the right order (leaf -> SubCA2 -> SubCA1 -> ... -> RootCA)
// Sub certificates must be in the right order (leaf -> SubCA2 -> SubCA1 -> RootCA)
issuingCertificate = getCertificate(subCertificates.getCertificate().get(0));
if (!isCertificateVerified(leafCertificate, issuingCertificate)) return false;
@ -541,16 +558,16 @@ public final class SecurityUtils {
/**
* Returns the mobility operator sub 2 certificate (MOSub2Certificate) which signs the contract
* certificate from the given keystore. The MOSub2Certificate is then used to verify the signature of
* sales tariffs.
* Returns the mobility operator Sub-CA 2 certificate (MOSubCA2 certificate) which can verify the signature of the
* contract certificate from the given keystore. The public key of the MOSub2Certificate is then used to verify
* the signature of sales tariffs.
*
* @param keyStoreFileName The relative path and file name of the keystore
* @return The X.509 mobility operator sub 2 certficiate (a certificate from a sub CA)
* @return The X.509 mobility operator Sub-CA2 certificate (a certificate from a Sub-CA)
*/
public static X509Certificate getMOSub2Certificate(String keyStoreFileName) {
public static X509Certificate getMOSubCA2Certificate(String keyStoreFileName) {
KeyStore keystore = getKeyStore(keyStoreFileName, GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString());
X509Certificate moSub2Certificate = null;
X509Certificate moSubCA2Certificate = null;
try {
Certificate[] certChain = keystore.getCertificateChain(GlobalValues.ALIAS_CONTRACT_CERTIFICATE.toString());
@ -561,33 +578,47 @@ public final class SecurityUtils {
X509Certificate x509Cert = getCertificate(certificate);
if (contractCertificate.getIssuerX500Principal().getName().equals(
x509Cert.getSubjectX500Principal().getName())) {
moSub2Certificate = x509Cert;
moSubCA2Certificate = x509Cert;
break;
}
}
} catch (KeyStoreException e) {
getLogger().error("KeyStoreException occurred while trying to get MOSub2 certificate");
getLogger().error("KeyStoreException occurred while trying to get MOSubCA2 certificate");
}
return moSub2Certificate;
return moSubCA2Certificate;
}
/**
* Returns the ECPublicKey instance from its raw bytes
* Returns the ECPublicKey instance from its encoded raw bytes.
* The first byte has the fixed value 0x04 indicating the uncompressed form.
* Therefore, the byte array must be of form: [0x04, x coord of point (32 bytes), y coord of point (32 bytes)]
*
* @param dhPublicKeyBytes The byte array representing the ECPublicKey instance
* @param publicKeyBytes The byte array representing the encoded raw bytes of the public key
* @return The ECPublicKey instance
*/
public static ECPublicKey getPublicKey(byte[] publicKeyBytes) {
// First we separate x and y of coordinates into separate variables
byte[] x = new byte[32];
byte[] y = new byte[32];
System.arraycopy(publicKeyBytes, 1, x, 0, 32);
System.arraycopy(publicKeyBytes, 33, y, 0, 32);
try {
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
ECPublicKey publicKey = (ECPublicKey) KeyFactory.getInstance("EC").generatePublic(keySpec);
return publicKey;
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
getLogger().error(e.getClass().getSimpleName() + " occurred when trying to get public key from raw bytes", e);
KeyFactory kf = KeyFactory.getInstance("EC");
AlgorithmParameters parameters = AlgorithmParameters.getInstance("EC");
parameters.init(new ECGenParameterSpec("secp256r1"));
ECParameterSpec ecParameterSpec = parameters.getParameterSpec(ECParameterSpec.class);
ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(x), new BigInteger(y)), ecParameterSpec);
ECPublicKey ecPublicKey = (ECPublicKey) kf.generatePublic(ecPublicKeySpec);
return ecPublicKey;
} catch (NoSuchAlgorithmException | InvalidParameterSpecException | InvalidKeySpecException e) {
getLogger().error(e.getClass().getSimpleName() + " occurred when trying to get public key from raw bytes", e);
return null;
}
}
}
@ -606,16 +637,17 @@ public final class SecurityUtils {
* 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());
dhPublicKey.setValue(getUncompressedSubjectPublicKey((ECPublicKey) ecdhKeyPair.getPublic()));
return dhPublicKey;
}
/**
* Returns the ECPrivateKey instance from its raw bytes
* Returns the ECPrivateKey instance from its raw bytes. Note that you must provide the "s" value of the
* private key, not e.g. the byte array from reading a PKCS#8 key file.
*
* @param privateKeyBytes The byte array representing the ECPrivateKey instance
* @param privateKeyBytes The byte array (the "s" value) of the private key
* @return The ECPrivateKey instance
*/
public static ECPrivateKey getPrivateKey(byte[] privateKeyBytes) {
@ -627,7 +659,7 @@ public final class SecurityUtils {
ECPrivateKeySpec ecPrivateKeySpec = new ECPrivateKeySpec(new BigInteger(privateKeyBytes), ecParameterSpec);
ECPrivateKey privateKey = (ECPrivateKey) KeyFactory.getInstance("EC").generatePrivate(ecPrivateKeySpec);
return privateKey;
} catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidParameterSpecException e) {
getLogger().error(e.getClass().getSimpleName() + " occurred when trying to get private key from raw bytes", e);
@ -639,19 +671,13 @@ public final class SecurityUtils {
/**
* Searches the given keystore for the private key. It is assumed that the given keystore holds
* only one private key entry whose alias is not known before, which is the case during certificate
* installation when the SECC receives a PKCS#12 container from a secondary actor encapsulating the
* installation when the SECC uses a PKCS#12 container encapsulating the
* contract certificate, its private key and an optional chain of intermediate CAs.
*
* @param keyStore The PKCS#12 keystore provided by the secondary actor
* @param keyStore The PKCS#12 keystore
* @return The private key contained in the given keystore as an ECPrivateKey
*/
public static ECPrivateKey getPrivateKey(KeyStore keyStore) {
/*
* For testing purposes, the respective PKCS12 container file chain has already been put in the
* resources folder. However, when implementing a real interface to a secondary actor's backend,
* the retrieval of a PKCS#12 container file must be done via some other online mechanism.
*/
ECPrivateKey privateKey = null;
try {
@ -673,30 +699,33 @@ public final class SecurityUtils {
/**
* Read a private key from a .key file and return it as an ECPrivateKey
* Reads the private key from an encrypted PKCS#8 file and returns it as an ECPrivateKey instance.
*
* @param A .key file containing the private key
* @return The private key stored in the .key file as an ECPrivateKey
* @param A PKCS#8 (.key) file containing the private key with value "s"
* @return The private key as an ECPrivateKey instance
*/
public static ECPrivateKey getPrivateKey(String keyFilePath, String password) {
File file = null;
FileInputStream fis = null;
public static ECPrivateKey getPrivateKey(String keyFilePath) {
Path fileLocation = Paths.get(keyFilePath);
byte[] pkcs8ByteArray;
try {
file = new File(keyFilePath);
fis = new FileInputStream(file);
byte[] privateKeyByteArray = new byte[(int) file.length()];
pkcs8ByteArray = Files.readAllBytes(fileLocation);
ECPrivateKey privateKey = getPrivateKey(privateKeyByteArray);
// The DER encoded private key is encrypted in PKCS#8. So we need to decrypt it first
PBEKeySpec pbeKeySpec = new PBEKeySpec(GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString().toCharArray());
EncryptedPrivateKeyInfo encryptedPrivKeyInfo = new EncryptedPrivateKeyInfo(pkcs8ByteArray);
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(encryptedPrivKeyInfo.getAlgName());
Key secret = secretKeyFactory.generateSecret(pbeKeySpec);
PKCS8EncodedKeySpec pkcs8PrivKeySpec = encryptedPrivKeyInfo.getKeySpec(secret);
ECPrivateKey privateKey = (ECPrivateKey) KeyFactory.getInstance("EC").generatePrivate(pkcs8PrivKeySpec);
fis.close();
return privateKey;
} catch (NullPointerException | IOException e) {
} catch (IOException | InvalidKeySpecException | NoSuchAlgorithmException | InvalidKeyException e) {
getLogger().error(e.getClass().getSimpleName() + " occurred while trying to access private key at " +
"location '" + keyFilePath + "'");
"location '" + keyFilePath + "'");
return null;
}
}
}
@ -807,7 +836,7 @@ public final class SecurityUtils {
digestMethod.setAlgorithm("http://www.w3.org/2001/04/xmlenc#sha256");
TransformType transform = new TransformType();
transform.setAlgorithm("http://www.w3.org/TR/canonical-exi");
transform.setAlgorithm("http://www.w3.org/TR/canonical-exi/");
TransformsType transforms = new TransformsType();
transforms.getTransform().add(transform);
@ -816,7 +845,6 @@ public final class SecurityUtils {
ReferenceType reference = new ReferenceType();
reference.setDigestMethod(digestMethod);
reference.setDigestValue(v);
reference.setId(k);
reference.setTransforms(transforms);
reference.setURI("#" + k);
@ -824,7 +852,7 @@ public final class SecurityUtils {
});
CanonicalizationMethodType canonicalizationMethod = new CanonicalizationMethodType();
canonicalizationMethod.setAlgorithm("http://www.w3.org/TR/canonical-exi");
canonicalizationMethod.setAlgorithm("http://www.w3.org/TR/canonical-exi/");
SignatureMethodType signatureMethod = new SignatureMethodType();
signatureMethod.setAlgorithm("http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256");
@ -1068,19 +1096,20 @@ public final class SecurityUtils {
/**
* Generates an elliptic curve Diffie-Hellman keypair.
* Generates an elliptic curve key pair using the named curve "secp256r1".
* This function is mainly used for the ECDH procedure.
*
* To use ECC (elliptic curve cryptography), SECC as well as EVCC must agree on all the elements
* defining the elliptic curve, that is, the "domain parameters" of the scheme. Such domain
* parameters are predefined by standardisation bodies and are commonly known as "standard curves"
* parameters are predefined by standardization bodies and are commonly known as "standard curves"
* or "named curves"; a named curve can be referenced either by name or by the unique object
* identifier defined in the standard documents. For the ISO/IEC 15118-2 document, the named curve
* "secp256r1" (SECG notation, see http://www.secg.org/sec2-v2.pdf) is used.
* See [V2G2-818] in ISO/IEC 15118-2 for further information.
*
* @return The Diffie-Hellman keypair for the elliptic curve 'secp256r1'
* @return An elliptic curve key pair according to the named curve 'secp256r1'
*/
public static KeyPair getECDHKeyPair() {
public static KeyPair getECKeyPair() {
KeyPair keyPair = null;
try {
@ -1097,13 +1126,32 @@ public final class SecurityUtils {
/**
* The shared secret which is to be generated with this function is used as input to a key derivation
* function. A key derivation function (KDF) is a deterministic algorithm to derive a key of a given
* The shared secret is computed using the domain parameters of the named curve "secp256r1", the private key
* part of the ephemeral key pair, and the OEM provisioning certiicates public key (in case of certificate
* installation) or the contract certificate's public key (in case of certificate update).
* The shared secret is used as input to a key derivation function.
* A key derivation function (KDF) is a deterministic algorithm to derive a key of a given
* size from some secret value. If two parties use the same shared secret value and the same KDF,
* they should always derive exactly the same key.
*
* @param privateKey The elliptic curve private key of a given certificate
* @param publicKey The elliptic curve Diffie-Hellman public key
* @param privateKey The private key of an EC key pair generated from the named curve "secp256r1".
*
* The mobility operator (MO) provides his ephemeral private key when using this function for
* generating the shared secret to encrypt the private key of the contract certificate.
*
* The EVCC provides the private key belonging to his OEM provisioning certificate's public key
* when using this function for generating the shared secret to decrypt the encrypted private key
* of the newly to be installed contract certificate.
* @param publicKey The public key of an EC key pair generated from the named curve "secp256r1"
*
* The mobility operator (MO) provides the static OEM provisioning certificate's (in case of
* CertificateInstallation) or old contract certificate's (in case of CertificateUpdate)
* public key when using this function for generating the shared secret to encrypt the private
* key of the contract certificate.
*
* The EVCC provides the ephemeral public key of the MO (coming with the CertificateInstallationRes
* or CertificateUpdateRes, respectively) when using this function for generating the shared secret
* to decrypt the encrypted private key of the newly to be installed contract certificate.
* @return The computed shared secret of the elliptic curve Diffie-Hellman key exchange protocol
*/
public static byte[] generateSharedSecret(ECPrivateKey privateKey, ECPublicKey publicKey) {
@ -1170,7 +1218,7 @@ public final class SecurityUtils {
/**
* Implementation of Concatenation Key Derivation Function <br/>
* Implementation of Concatenation Key Derivation Function
* http://csrc.nist.gov/publications/nistpubs/800-56A/SP800-56A_Revision1_Mar08-2007.pdf
*
* Author: NimbusDS Lai Xin Chu and Vladimir Dzhuvinov
@ -1192,7 +1240,7 @@ public final class SecurityUtils {
}
int counter = 1;
byte[] counterInBytes = intToFourBytes(counter);
byte[] counterInBytes = ByteUtils.intToFourBytes(counter);
if ((counterInBytes.length + z.length + otherInfo.length) * 8 > MAX_HASH_INPUTLEN) {
getLogger().error("Key derivation failed");
@ -1201,7 +1249,7 @@ public final class SecurityUtils {
for (int i = 0; i <= reps; i++) {
md.reset();
md.update(intToFourBytes(i+1));
md.update(ByteUtils.intToFourBytes(i+1));
md.update(z);
md.update(otherInfo);
@ -1221,16 +1269,6 @@ public final class SecurityUtils {
}
private static byte[] intToFourBytes(int i) {
byte[] res = new byte[4];
res[0] = (byte) (i >>> 24);
res[1] = (byte) ((i >>> 16) & 0xFF);
res[2] = (byte) ((i >>> 8) & 0xFF);
res[3] = (byte) (i & 0xFF);
return res;
}
private static ContractSignatureEncryptedPrivateKeyType getContractSignatureEncryptedPrivateKey(
SecretKey sessionKey, ECPrivateKey contractCertPrivateKey) {
ContractSignatureEncryptedPrivateKeyType encryptedPrivateKey = new ContractSignatureEncryptedPrivateKeyType();
@ -1242,22 +1280,22 @@ public final class SecurityUtils {
/**
* Encrypts the private key of the contract certificate which is to be sent to the EVCC. First, the
* shared secret based on the ECDH paramters is calculated, then the symmetric session key with which
* shared secret based on the ECDH parameters is calculated, then the symmetric session key with which
* the private key of the contract certificate is to be encrypted.
*
* @param certificateECPublicKey The public key of either the OEM provisioning certificate (in case of
* CertificateInstallation) or the to be updated contract certificate
* (in case of CertificateUpdate)
* @param ecdhKeyPair The ECDH keypair
* @param ecKeyPair The EC keypair
* @param contractCertPrivateKey The private key of the contract certificate
* @return The encrypted private key of the to be installed contract certificate
*/
public static ContractSignatureEncryptedPrivateKeyType encryptContractCertPrivateKey(
ECPublicKey certificateECPublicKey,
KeyPair ecdhKeyPair,
KeyPair ecKeyPair,
ECPrivateKey contractCertPrivateKey) {
// Generate the shared secret by using the public key of either OEMProvCert or ContractCert
byte[] sharedSecret = generateSharedSecret((ECPrivateKey) ecdhKeyPair.getPrivate(), certificateECPublicKey);
byte[] sharedSecret = generateSharedSecret((ECPrivateKey) ecKeyPair.getPrivate(), certificateECPublicKey);
if (sharedSecret == null) {
getLogger().error("Shared secret could not be generated");
@ -1348,13 +1386,6 @@ public final class SecurityUtils {
byte[] dhPublicKey,
byte[] contractSignatureEncryptedPrivateKey,
ECPrivateKey certificateECPrivateKey) {
// Generate ECDH key pair
KeyPair ecdhKeyPair = getECDHKeyPair();
if (ecdhKeyPair == null) {
getLogger().error("ECDH keypair could not be generated");
return null;
}
// Generate shared secret
ECPublicKey publicKey = (ECPublicKey) getPublicKey(dhPublicKey);
byte[] sharedSecret = generateSharedSecret(certificateECPrivateKey, publicKey);
@ -1432,6 +1463,17 @@ public final class SecurityUtils {
}
/**
* Useful for debugging purposes when verifying a signature and trying to figure out where it went wrong if
* a signature verification failed.
*
* @return
*/
// public static byte[] decryptSignature(byte[] signature, ECPublicKey publicKey) {
//
// }
/**
* Returns the EMAID (e-mobility account identifier) from the contract certificate.
*
@ -1568,7 +1610,7 @@ public final class SecurityUtils {
* @param digestForSignedInfoElement True if a digest for the SignedInfoElement of the header's signature is to be generated, false otherwise
* @return The SHA-256 digest for message or field
*/
public static byte[] generateDigest(Object messageOrField, boolean digestForSignedInfoElement) {
public static byte[] generateDigest(Object messageOrField) {
JAXBElement jaxbElement = MiscUtils.getJaxbElement(messageOrField);
byte[] encoded;
@ -1579,17 +1621,35 @@ public final class SecurityUtils {
* When creating the signature value for the SignedInfoElement, we need to use the XMLdsig schema,
* whereas for creating the reference elements of the signature, we need to use the V2G_CI_MsgDef schema.
*/
if (digestForSignedInfoElement) encoded = getExiCodec().encodeEXI(jaxbElement, GlobalValues.SCHEMA_PATH_XMLDSIG.toString());
if (messageOrField instanceof SignedInfoType) encoded = getExiCodec().encodeEXI(jaxbElement, GlobalValues.SCHEMA_PATH_XMLDSIG.toString());
else encoded = getExiCodec().encodeEXI(jaxbElement, GlobalValues.SCHEMA_PATH_MSG_DEF.toString());
// Do not use the schema-informed fragment grammar option for other EXI encodings (message bodies)
getExiCodec().setFragment(false);
if (encoded == null) {
getLogger().error("Digest could not be generated because of EXI encoding problem");
return null;
}
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(encoded);
byte[] digest = md.digest();
return md.digest();
if (showSignatureVerificationLog) {
/*
* Show Base64 encoding of digests only for reference elements, not for the SignedInfo element.
* The hashed SignedInfo element is input for ECDSA before the final signature value gets Base64 encoded.
*/
if ( !(messageOrField instanceof SignedInfoType) ) {
getLogger().debug("\n"
+ "\tDigest generated for reference element " + messageOrField.getClass().getSimpleName() + ": " + ByteUtils.toHexString(digest) + "\n"
+ "\tBase64 encoding of digest: " + Base64.getEncoder().encodeToString(digest));
}
}
return digest;
} catch (NoSuchAlgorithmException e) {
getLogger().error("NoSuchAlgorithmException occurred while trying to create digest", e);
return null;
@ -1598,27 +1658,31 @@ public final class SecurityUtils {
/**
* Signs the SignatureInfo element of the V2GMessage header.
* Signs the SignedInfo element of the V2GMessage header.
*
* @param signatureInfo The SignatureInfo given as a byte array
* @param ecPrivateKey The private key which is used to sign the SignatureInfo element
* @return The signed SignatureInfo element given as a byte array
* @param signedInfoElementExi The EXI-encoded SignedInfo element given as a byte array
* @param ecPrivateKey The private key which is used to sign the SignedInfo element
* @return The signature value for the SignedInfo element given as a byte array
*/
public static byte[] signSignatureInfo(byte[] signatureInfo, ECPrivateKey ecPrivateKey) {
public static byte[] signSignedInfoElement(byte[] signedInfoElementExi, ECPrivateKey ecPrivateKey) {
try {
Signature ecdsa = Signature.getInstance("SHA256withECDSA");
Signature ecdsa = Signature.getInstance("SHA256withECDSA", "SunEC");
ecdsa.initSign(ecPrivateKey);
ecdsa.update(signatureInfo);
ecdsa.update(signedInfoElementExi);
byte[] signature = ecdsa.sign();
return signature;
} catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
// Java operates on DER encoded signatures, but we must send the raw r and s values as signature
byte[] rawSignature = getRawSignatureFromDEREncoding(signature);
return rawSignature;
} catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException | NoSuchProviderException e) {
getLogger().error(e.getClass().getSimpleName() + " occurred while trying to create signature", e);
return null;
}
}
/**
* Verifies the signature given in the received header of an EVCC or SECC message
*
@ -1626,16 +1690,37 @@ public final class SecurityUtils {
* @param verifyXMLSigRefElements The HashMap of signature IDs and digest values of the message body
* or fields respectively of the received message (to cross-check against the XML reference
* elements contained in the received message header)
* @param ecPublicKey The public key corresponding to the private key which was used for the signature
* @param verifyCert The certificate holding the public key corresponding to the private key which was used
* for the signature. Given as a byte array, this function will call verifySignature() with an X509Certificate
* as last parameter.
* @return True, if digest validation of all XML reference elements and signature validation was
* successful, false otherwise
*/
public static boolean verifySignature(
SignatureType signature,
HashMap<String, byte[]> verifyXMLSigRefElements,
byte[] verifyCert) {
X509Certificate x509VerifyCert = getCertificate(verifyCert);
return verifySignature(signature, verifyXMLSigRefElements, x509VerifyCert);
}
/**
* Verifies the signature given in the received header of an EVCC or SECC message
*
* @param signature The received header's signature
* @param verifyXMLSigRefElements The HashMap of signature IDs and digest values of the message body
* or fields respectively of the received message (to cross-check against the XML reference
* elements contained in the received message header)
* @param verifyCert The certificate holding the public key corresponding to the private key which was used for the signature
* @return True, if digest validation of all XML reference elements and signature validation was
* successful, false otherwise
*/
public static boolean verifySignature(
SignatureType signature,
HashMap<String, byte[]> verifyXMLSigRefElements,
ECPublicKey ecPublicKey) {
X509Certificate verifyCert) {
byte[] calculatedReferenceDigest;
boolean match;
boolean messageDigestsEqual;
/*
* 1. step:
@ -1644,27 +1729,35 @@ public final class SecurityUtils {
*/
for (String id : verifyXMLSigRefElements.keySet()) {
getLogger().debug("Verifying digest for element '" + id + "'");
match = false;
messageDigestsEqual = false;
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 == 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
// We need to check the URI attribute, not the Id attribute. But the Id must be set to sth. different than the IDs used in the body!
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 (reference.getURI().equals('#' + id)) {
messageDigestsEqual = MessageDigest.isEqual(reference.getDigestValue(), calculatedReferenceDigest);
if (showSignatureVerificationLog) {
getLogger().debug("\n"
+ "\tReceived digest of reference with ID '" + id + "': " + ByteUtils.toHexString(reference.getDigestValue()) + "\n"
+ "\tCalculated digest of reference with ID '" + id + "': " + ByteUtils.toHexString(calculatedReferenceDigest) + "\n"
+ "\t==> Match: " + messageDigestsEqual);
}
}
}
if (!match) {
if (!messageDigestsEqual) {
getLogger().error("No matching signature found for ID '" + id + "' and digest value " +
ByteUtils.toHexString(calculatedReferenceDigest));
return false;
@ -1674,24 +1767,223 @@ public final class SecurityUtils {
/*
* 2. step:
* Check the signature value from the header with the computed signature value
* Check the signature itself
*/
byte[] computedSignedInfoDigest = generateDigest(signature.getSignedInfo(), true);
ECPublicKey ecPublicKey = (ECPublicKey) verifyCert.getPublicKey();
Signature ecdsa;
boolean verified;
try {
getLogger().debug("Verifying signature of signed info element");
getLogger().debug("Verifying signature of SignedInfo element ...");
// Check if signature verification logging is to be shown (for debug purposes)
if (showSignatureVerificationLog) showSignatureVerificationLog(verifyCert, signature, ecPublicKey);
ecdsa = Signature.getInstance("SHA256withECDSA");
// The Signature object needs to be initialized by setting it into the VERIFY state with the public key
ecdsa.initVerify(ecPublicKey);
ecdsa.update(computedSignedInfoDigest);
verified = ecdsa.verify(signature.getSignatureValue().getValue());
// The data to be signed needs to be supplied to the Signature object
byte[] exiEncodedSignedInfo = getExiCodec().getExiEncodedSignedInfo(signature.getSignedInfo());
ecdsa.update(exiEncodedSignedInfo);
// Java operates on DER encoded signature values, but the sent signature consists of the raw r and s value
byte[] signatureValue = signature.getSignatureValue().getValue();
byte[] derEncodedSignatureValue = getDEREncodedSignature(signatureValue);
// The verify() method will do both, the decryption and SHA256 validation. So don't hash separately before verifying
verified = ecdsa.verify(derEncodedSignatureValue);
return verified;
} catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
getLogger().error(e.getClass().getSimpleName() + " occurred while trying to verify signature value", e);
return false;
}
}
/**
* Shows some extended logging while verifying a signature for debugging purposes.
* @param verifyCert The X509Certificate whose public key is used to verify the signature, used for printing the
* certificate's subject value
* @param signature The signature contained in the header of the V2GMessage
* @param ecPublicKey The public key used to verify the signature
*/
private static void showSignatureVerificationLog(X509Certificate verifyCert, SignatureType signature, ECPublicKey ecPublicKey) {
byte[] computedSignedInfoDigest = generateDigest(signature.getSignedInfo());
byte[] receivedSignatureValue = signature.getSignatureValue().getValue();
getLogger().debug("\n"
+ "\tCertificate used to verify signature: " + verifyCert.getSubjectX500Principal().getName() + "\n"
+ "\tPublic key used to verify signature: " + ByteUtils.toHexString(getUncompressedSubjectPublicKey(ecPublicKey)) + "\n"
+ "\tReceived signature value: " + ByteUtils.toHexString(receivedSignatureValue) + " (Base64: " + Base64.getEncoder().encodeToString(receivedSignatureValue) + ")\n"
+ "\tCalculated digest of SignedInfo element: " + ByteUtils.toHexString(computedSignedInfoDigest));
}
/**
* Java puts some encoding information into the ECPublicKey.getEncoded().
* This method returns the raw ECPoint (the x and y coordinate of the public key) in uncompressed form
* (with the 0x04 as first octet), aka the Subject Public Key according to RFC 5480
*
* @param ecPublicKey The ECPublicKey provided by Java
* @return The uncompressed Subject Public Key (with the first octet set to 0x04)
*/
public static byte[] getUncompressedSubjectPublicKey(ECPublicKey ecPublicKey) {
byte[] uncompressedPubKey = new byte[65];
uncompressedPubKey[0] = 0x04;
byte[] affineX = ecPublicKey.getW().getAffineX().toByteArray();
byte[] affineY = ecPublicKey.getW().getAffineY().toByteArray();
// If the length is 33 bytes, then the first byte is a 0x00 which is to be omitted
if (affineX.length == 33)
System.arraycopy(affineX, 1, uncompressedPubKey, 1, 32);
else
System.arraycopy(affineX, 0, uncompressedPubKey, 1, 32);
if (affineY.length == 33)
System.arraycopy(affineY, 1, uncompressedPubKey, 33, 32);
else
System.arraycopy(affineY, 0, uncompressedPubKey, 33, 32);
return uncompressedPubKey;
}
/**
* An ECDSA signature consists of two integers s and r, each of the bit length equal to the curve size.
* When Java is creating an ECDSA signature, it is encoding it in the DER (Distinguished Encoding Rules) format.
* But in ISO 15118, we do not expect DER encoded signatures. Thus, this function takes the DER encoded signature
* as input and returns the raw r and s integer values of the signature.
*
* @param derEncodedSignature The DER encoded signature as a result from java.security.Signature.sign()
* @return A byte array containing only the r and s value of the signature
*/
public static byte[] getRawSignatureFromDEREncoding(byte[] derEncodedSignature) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] r = new byte[32];
byte[] s = new byte[32];
// Length of r is encoded in the fourth byte (either 32 (hex: 0x20) or 33 (hex: 0x21))
int lengthOfR = (int) derEncodedSignature[3];
// Length of r is encoded in the second byte AFTER r (either 32 (hex: 0x20) or 33 (hex: 0x21))
int lengthOfS = (int) derEncodedSignature[lengthOfR + 5];
// If r is made up of 33 bytes, then we need to skip the first fill byte (0x00) of r
if (lengthOfR == 33) System.arraycopy(derEncodedSignature, 5, r, 0, 32);
else System.arraycopy(derEncodedSignature, 4, r, 0, 32);
// If r is made up of 33 bytes (hex value 0x21), then we need to skip the first fill byte (0x00) or r
if (lengthOfS == 33) System.arraycopy(derEncodedSignature, lengthOfR + 7, s, 0, 32);
else System.arraycopy(derEncodedSignature, lengthOfR + 6, s, 0, 32);
try {
baos.write(r);
baos.write(s);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
byte[] rawRAndS = baos.toByteArray();
return rawRAndS;
}
/**
* When encoded in DER, the signature - holding the
* x-coordinate of the elliptic curve point in the value "r"
* and the
* y-coordinate of the elliptic curve point in the value "s"
* - becomes the following sequence of bytes (in total 70 bytes instead of 64 bytes):
*
* 0x30 b1 0x02 b2 (vr) 0x02 b3 (vs)
*
* where:
*
* - 0x30 is always the first byte of the DER encoded signature format (ASN.1 tag for sequence)
* - b1 is a single byte value, encoding the length in bytes of the remaining list of bytes
* (from the first 0x02 to the end of the encoding); is a value between 0x44 and 0x46
* - 0x02 is a fixed value indicating that an integer value will follow (ASN.1 tag for int)
* - b2 is a single byte value, encoding the length in bytes of (vr);
* (either 0x20 (32 bytes) or 0x21 (33 bytes), depending on whether an optional fill byte 0x00 is used as most significant byte)
* - (vr) is the signed big-endian encoding of the value "r", of minimal length;
* - 0x02 is a fixed value indicating that an integer value will follow (ASN.1 tag for int)
* - b3 is a single byte value, encoding the length in bytes of (vs);
* (either 0x20 (32 bytes) or 0x21 (33 bytes), depending on whether an optional fill byte 0x00 is used as most significant byte)
* - (vs) is the signed big-endian encoding of the value "s", of minimal length.
*/
private static byte[] getDEREncodedSignature (byte[] signatureValue) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// First we separate x and y of coordinates into separate variables
byte[] r = new byte[32];
byte[] s = new byte[32];
System.arraycopy(signatureValue, 0, r, 0, 32);
System.arraycopy(signatureValue, 32, s, 0, 32);
int neededByteLength = signatureValue.length + 6; // 6 bytes for the header
boolean isFillByteForR = false;
boolean isFillByteForS = false;
if (r[0] < 0) { // checks if the value is negative which is equivalent to r[0] is bigger than 0x7f
isFillByteForR = true;
neededByteLength += 1;
}
if (s[0] < 0) {
isFillByteForS = true;
neededByteLength += 1;
}
baos.write(0x30);
baos.write(neededByteLength - 2);
baos.write(0x02);
try {
if (isFillByteForR) {
baos.write(0x21);
baos.write(0x00);
baos.write(r);
} else {
baos.write(0x20);
baos.write(r);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return verified;
baos.write(0x02);
try {
if (isFillByteForS) {
baos.write(0x21);
baos.write(0x00);
baos.write(s);
} else {
baos.write(0x20);
baos.write(s);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
byte[] derEncodedSignature = baos.toByteArray();
try {
baos.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return derEncodedSignature;
}