SecurityUtils.java 98 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296
  1. /*******************************************************************************
  2. * The MIT License (MIT)
  3. *
  4. * Copyright (c) 2015 - 2019 Dr. Marc Mültin (V2G Clarity)
  5. *
  6. * Permission is hereby granted, free of charge, to any person obtaining a copy
  7. * of this software and associated documentation files (the "Software"), to deal
  8. * in the Software without restriction, including without limitation the rights
  9. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. * copies of the Software, and to permit persons to whom the Software is
  11. * furnished to do so, subject to the following conditions:
  12. *
  13. * The above copyright notice and this permission notice shall be included in
  14. * all copies or substantial portions of the Software.
  15. *
  16. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  22. * THE SOFTWARE.
  23. *******************************************************************************/
  24. package com.v2gclarity.risev2g.shared.utils;
  25. import java.io.ByteArrayInputStream;
  26. import java.io.ByteArrayOutputStream;
  27. import java.io.FileInputStream;
  28. import java.io.FileNotFoundException;
  29. import java.io.FileOutputStream;
  30. import java.io.IOException;
  31. import java.io.InputStream;
  32. import java.math.BigInteger;
  33. import java.nio.file.Files;
  34. import java.nio.file.Path;
  35. import java.nio.file.Paths;
  36. import java.security.AlgorithmParameters;
  37. import java.security.InvalidAlgorithmParameterException;
  38. import java.security.InvalidKeyException;
  39. import java.security.Key;
  40. import java.security.KeyFactory;
  41. import java.security.KeyManagementException;
  42. import java.security.KeyPair;
  43. import java.security.KeyPairGenerator;
  44. import java.security.KeyStore;
  45. import java.security.KeyStoreException;
  46. import java.security.MessageDigest;
  47. import java.security.NoSuchAlgorithmException;
  48. import java.security.NoSuchProviderException;
  49. import java.security.PublicKey;
  50. import java.security.SecureRandom;
  51. import java.security.Signature;
  52. import java.security.SignatureException;
  53. import java.security.UnrecoverableKeyException;
  54. import java.security.cert.Certificate;
  55. import java.security.cert.CertificateEncodingException;
  56. import java.security.cert.CertificateException;
  57. import java.security.cert.CertificateExpiredException;
  58. import java.security.cert.CertificateFactory;
  59. import java.security.cert.CertificateNotYetValidException;
  60. import java.security.cert.X509Certificate;
  61. import java.security.interfaces.ECPrivateKey;
  62. import java.security.interfaces.ECPublicKey;
  63. import java.security.spec.ECGenParameterSpec;
  64. import java.security.spec.ECParameterSpec;
  65. import java.security.spec.ECPoint;
  66. import java.security.spec.ECPrivateKeySpec;
  67. import java.security.spec.ECPublicKeySpec;
  68. import java.security.spec.InvalidKeySpecException;
  69. import java.security.spec.InvalidParameterSpecException;
  70. import java.security.spec.PKCS8EncodedKeySpec;
  71. import java.util.ArrayList;
  72. import java.util.Calendar;
  73. import java.util.Date;
  74. import java.util.Enumeration;
  75. import java.util.HashMap;
  76. import java.util.List;
  77. import java.util.Map;
  78. import java.util.concurrent.TimeUnit;
  79. import javax.crypto.BadPaddingException;
  80. import javax.crypto.Cipher;
  81. import javax.crypto.EncryptedPrivateKeyInfo;
  82. import javax.crypto.IllegalBlockSizeException;
  83. import javax.crypto.KeyAgreement;
  84. import javax.crypto.NoSuchPaddingException;
  85. import javax.crypto.SecretKey;
  86. import javax.crypto.SecretKeyFactory;
  87. import javax.crypto.spec.IvParameterSpec;
  88. import javax.crypto.spec.PBEKeySpec;
  89. import javax.crypto.spec.SecretKeySpec;
  90. import javax.naming.InvalidNameException;
  91. import javax.naming.ldap.LdapName;
  92. import javax.naming.ldap.Rdn;
  93. import javax.net.ssl.KeyManager;
  94. import javax.net.ssl.KeyManagerFactory;
  95. import javax.net.ssl.SSLContext;
  96. import javax.net.ssl.TrustManager;
  97. import javax.net.ssl.TrustManagerFactory;
  98. import javax.security.auth.x500.X500Principal;
  99. import javax.xml.bind.JAXBElement;
  100. import org.apache.logging.log4j.LogManager;
  101. import org.apache.logging.log4j.Logger;
  102. import com.v2gclarity.risev2g.shared.enumerations.GlobalValues;
  103. import com.v2gclarity.risev2g.shared.enumerations.PKI;
  104. import com.v2gclarity.risev2g.shared.exiCodec.ExiCodec;
  105. import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CanonicalizationMethodType;
  106. import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CertificateChainType;
  107. import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ContractSignatureEncryptedPrivateKeyType;
  108. import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.DiffieHellmanPublickeyType;
  109. import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.DigestMethodType;
  110. import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EMAIDType;
  111. import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ListOfRootCertificateIDsType;
  112. import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ReferenceType;
  113. import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
  114. import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SignatureMethodType;
  115. import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SignatureType;
  116. import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SignedInfoType;
  117. import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SubCertificatesType;
  118. import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.TransformType;
  119. import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.TransformsType;
  120. import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.X509IssuerSerialType;
  121. import java.util.Base64;
  122. public final class SecurityUtils {
  123. /*
  124. * Add VM (virtual machine) argument "-Djavax.net.debug=ssl" if you want more detailed debugging output
  125. */
  126. static Logger logger = LogManager.getLogger(SecurityUtils.class.getSimpleName());
  127. static ExiCodec exiCodec;
  128. static boolean showSignatureVerificationLog = ((boolean) MiscUtils.getPropertyValue("signature.verification.showlog"));
  129. public static enum ContractCertificateStatus {
  130. UPDATE_NEEDED,
  131. INSTALLATION_NEEDED,
  132. OK,
  133. UNKNOWN // is used as default for communication session context
  134. }
  135. public static Logger getLogger() {
  136. return logger;
  137. }
  138. /**
  139. * Returns the standard JKS keystore which holds the respective credentials (private key and
  140. * certificate chain) for the EVCC or SECC (whoever calls this method).
  141. *
  142. * The keystore file itself must reside outside the JAR file, at the same level as the JAR file itself,
  143. * because
  144. * a) at least the evccKeystore needs to be editable when installing the contract certificate (JAR file is read-only), and
  145. * b) it is very likely that private keys and certificate chains might be stored separately in a secure hardware module.
  146. * Therefore, the file is not loaded with getResourceAsStream(), but with a FileInputStream.
  147. *
  148. * @param keyStorePath The relative path and file name of the keystore
  149. * @param keyStorePassword The password which protects the keystore
  150. * @return The respective keystore
  151. */
  152. public static KeyStore getKeyStore(String keyStorePath, String keyStorePassword) {
  153. FileInputStream keyStore;
  154. try {
  155. keyStore = new FileInputStream(keyStorePath);
  156. return getKeyStore(keyStore, keyStorePassword, "jks");
  157. } catch (FileNotFoundException e) {
  158. getLogger().error("Keystore file location '" + keyStorePath + "' not found (FileNotFoundException).");
  159. return null;
  160. }
  161. }
  162. /**
  163. * Returns the standard JKS truststore which holds the respective trusted certificates for the EVCC
  164. * or SECC (whoever calls this method).
  165. *
  166. * The truststore file itself must reside outside the JAR file, at the same level as the JAR file itself,
  167. * because
  168. * a) at least the evccKeystore needs to be editable when installing the contract certificate (JAR file is read-only), and
  169. * b) it is very likely that private keys and certificate chains might be stored separately in a secure hardware module.
  170. * Therefore, the file is not loaded with getResourceAsStream(), but with a FileInputStream.
  171. *
  172. * @param trustStorePath The relative path and file name of the truststore
  173. * @param trustStorePassword The password which protects the truststore
  174. * @return The respective truststore
  175. */
  176. public static KeyStore getTrustStore(String trustStorePath, String trustStorePassword) {
  177. FileInputStream trustStore;
  178. try {
  179. trustStore = new FileInputStream(trustStorePath);
  180. return getKeyStore(trustStore, trustStorePassword, "jks");
  181. } catch (FileNotFoundException e) {
  182. getLogger().error("Truststore file location '" + trustStorePath + "' not found (FileNotFoundException).");
  183. return null;
  184. }
  185. }
  186. /**
  187. * Returns a PKCS#12 container which holds the respective credentials (private key and certificate chain)
  188. *
  189. * @param pkcs12Path The relative path and file name of the PKCS#12 container
  190. * @param password The password which protects the PKCS#12 container
  191. * @return The respective keystore
  192. */
  193. public static KeyStore getPKCS12KeyStore(String pkcs12Path, String password) {
  194. FileInputStream fis = null;
  195. try {
  196. fis = new FileInputStream(pkcs12Path);
  197. return getKeyStore(fis, password, "pkcs12");
  198. } catch (FileNotFoundException e) {
  199. getLogger().error("FileNotFoundException occurred while trying to access PKCS#12 container at " +
  200. "location '" + pkcs12Path + "'");
  201. return null;
  202. }
  203. }
  204. /**
  205. * Returns a standard keystore which holds the respective credentials (private key and certificate chain).
  206. *
  207. * @param keyStoreIS The input stream of the keystore
  208. * @param keyStorePassword The password which protects the keystore
  209. * @param keyStoreType The type of the keystore, either "jks" or "pkcs12"
  210. * @return The respective keystore
  211. */
  212. private static KeyStore getKeyStore(InputStream keyStoreIS, String keyStorePassword, String keyStoreType) {
  213. KeyStore keyStore = null;
  214. try {
  215. keyStore = KeyStore.getInstance(keyStoreType);
  216. keyStore.load(keyStoreIS, keyStorePassword.toCharArray());
  217. keyStoreIS.close();
  218. return keyStore;
  219. } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException |
  220. IOException | NullPointerException e) {
  221. getLogger().error(e.getClass().getSimpleName() + " occurred while trying to load keystore", e);
  222. }
  223. return null;
  224. }
  225. /**
  226. * Checks whether the given certificate is currently valid with regards to date and time.
  227. *
  228. * @param certificate The X509Certificiate to be checked for validity
  229. * @return ResponseCode FAILED_CertificateExpired, if the certificate is expired. FAILED, if the certificate is
  230. * not yet valid, since there is no other proper response code available. OK, otherwise.
  231. */
  232. public static ResponseCodeType verifyValidityPeriod(X509Certificate certificate) {
  233. try {
  234. certificate.checkValidity();
  235. return ResponseCodeType.OK;
  236. } catch (CertificateExpiredException e) {
  237. X500Principal subject = certificate.getSubjectX500Principal();
  238. getLogger().warn("Certificate with distinguished name '" + subject.getName() +
  239. "' already expired (not after " + certificate.getNotAfter() + ")");
  240. return ResponseCodeType.FAILED_CERTIFICATE_EXPIRED;
  241. } catch (CertificateNotYetValidException e) {
  242. X500Principal subject = certificate.getSubjectX500Principal();
  243. getLogger().warn("Certificate with distinguished name '" + subject.getName() +
  244. "' not yet valid (not before " + certificate.getNotBefore() + ")");
  245. return ResponseCodeType.FAILED;
  246. }
  247. }
  248. /**
  249. * Domain Component restrictions: <br/>
  250. * - SECC certificate: "CPO" (verification by EVCC) <br/>
  251. * - CPS leaf certificate: "CPS" (verification by EVCC) <br/>
  252. * - OEM Provisioning Certificate: "OEM" (verification by provisioning service (neither EVCC nor SECC))
  253. *
  254. * @param certificate The X509Certificiate to be checked for validity
  255. * @param domainComponent The domain component to be checked for in the distinguished name of the certificate
  256. * @return True, if the given domain component is present in the distinguished name, false otherwise
  257. */
  258. public static boolean verifyDomainComponent(X509Certificate certificate, String domainComponent) {
  259. String dn = certificate.getSubjectX500Principal().getName();
  260. LdapName ln;
  261. try {
  262. ln = new LdapName(dn);
  263. for (Rdn rdn : ln.getRdns()) {
  264. if (rdn.getType().equalsIgnoreCase("DC") && rdn.getValue().equals(domainComponent)) {
  265. return true;
  266. }
  267. }
  268. } catch (InvalidNameException e) {
  269. getLogger().error("InvalidNameException occurred while trying to check domain component of certificate", e);
  270. }
  271. getLogger().error("Expected domain component (DC) '" + domainComponent + "' not found in certificate "
  272. + "with distinguished name '" + dn + "'");
  273. return false;
  274. }
  275. /**
  276. * Checks how many days a given certificate is still valid.
  277. * If the certificate is not valid any more, a negative number will be returned according to the number
  278. * of days the certificate is already expired.
  279. *
  280. * @param certificate The X509Certificiate to be checked for validity period
  281. * @return The number of days the given certificate is still valid, a negative number if already expired.
  282. */
  283. public static short getValidityPeriod(X509Certificate certificate) {
  284. Date today = Calendar.getInstance().getTime();
  285. Date certificateExpirationDate = certificate.getNotAfter();
  286. long diff = certificateExpirationDate.getTime() - today.getTime();
  287. return (short) TimeUnit.DAYS.convert(diff, TimeUnit.MILLISECONDS);
  288. }
  289. /**
  290. * Executes the following validity checks:
  291. * <br/><br/>
  292. * 1. Verifies the signature for each certificate in the given certificate chain all the way up to the trust
  293. * anchor. Certificates in certificate chain must be in the right order (leaf -> Sub-CA2 -> Sub-CA1) <br/>
  294. * 2. Verifies whether the given certificate is currently valid with regards to date and time.<br/>
  295. * 3. Verifies that certificate attributes are set correctly, depending on the PKI the certificate chain belongs to
  296. *
  297. * @param certChain The certificate chain to iterate over to check for validity
  298. * @param trustStoreFileName The relative path and file name of the truststore
  299. * @param pki The Public Key Infrastructure to which the certChain belongs (a PKI enumeration value)
  300. * @return ResponseCode applicable to the verification steps
  301. */
  302. public static ResponseCodeType verifyCertificateChain(
  303. CertificateChainType certChain,
  304. String trustStoreFileName,
  305. PKI pki) {
  306. X509Certificate leafCertificate = null;
  307. X509Certificate subCA1Certificate = null;
  308. X509Certificate subCA2Certificate = null;
  309. ResponseCodeType responseCode = null;
  310. // Get leaf certificate
  311. if (certChain != null) {
  312. leafCertificate = getCertificate(certChain.getCertificate());
  313. } else {
  314. getLogger().error("Signature verification failed because provided certificate chain is empty (null)");
  315. return ResponseCodeType.FAILED_CERT_CHAIN_ERROR;
  316. }
  317. // Get Sub-CA certificates
  318. if (leafCertificate != null) {
  319. SubCertificatesType subCertificates = certChain.getSubCertificates();
  320. if (subCertificates != null && subCertificates.getCertificate().size() != 0) {
  321. subCA2Certificate = getCertificate(subCertificates.getCertificate().get(0));
  322. if (subCertificates.getCertificate().size() == 2)
  323. subCA1Certificate = getCertificate(subCertificates.getCertificate().get(1));
  324. } else {
  325. getLogger().error("Signature verification failed because no Sub-CA certificates available in provided "
  326. + "certificate chain");
  327. return ResponseCodeType.FAILED_CERT_CHAIN_ERROR;
  328. }
  329. } else {
  330. getLogger().error("Signature verification failed because no leaf certificate available in provided "
  331. + "certificate chain");
  332. return ResponseCodeType.FAILED_CERT_CHAIN_ERROR;
  333. }
  334. /*
  335. * ****************
  336. * SIGNATURE CHECKS
  337. * ****************
  338. */
  339. // Check signature of leaf certificate
  340. if (!verifySignature(leafCertificate, subCA2Certificate)) return ResponseCodeType.FAILED_CERT_CHAIN_ERROR;
  341. // Check signature of Sub-CA 2 and optionally, if present, Sub-CA 2 certificate
  342. if (subCA1Certificate != null) {
  343. if (!verifySignature(subCA2Certificate, subCA1Certificate)) return ResponseCodeType.FAILED_CERT_CHAIN_ERROR;
  344. if (!verifySignature(subCA1Certificate, trustStoreFileName)) return ResponseCodeType.FAILED_CERT_CHAIN_ERROR;
  345. } else {
  346. // In case there is only one intermediate certificate (profile of Sub-CA 2)
  347. if (!verifySignature(subCA2Certificate, trustStoreFileName)) return ResponseCodeType.FAILED_CERT_CHAIN_ERROR;
  348. }
  349. /*
  350. * **********************
  351. * VALIDITY PERIOD CHECKS
  352. * **********************
  353. */
  354. ResponseCodeType validityResponseCode = null;
  355. // Check validity of leaf certificate
  356. validityResponseCode = verifyValidityPeriod(leafCertificate);
  357. if (!validityResponseCode.equals(ResponseCodeType.OK)) return validityResponseCode;
  358. // Check validity of Sub-CA2 certificate
  359. validityResponseCode = verifyValidityPeriod(subCA2Certificate);
  360. if (!validityResponseCode.equals(ResponseCodeType.OK)) return validityResponseCode;
  361. // Check validity of Sub-CA1 certificate, if present
  362. if (subCA1Certificate != null) {
  363. validityResponseCode = verifyValidityPeriod(subCA1Certificate);
  364. if (!validityResponseCode.equals(ResponseCodeType.OK)) return validityResponseCode;
  365. }
  366. /*
  367. * ***********************************
  368. * COMMON CERTIFICATE ATTRIBUTES CHECK
  369. * ***********************************
  370. */
  371. // Check pathLenContraint (maximum number of non-self-issued intermediate certificates that may follow this certificate)
  372. if (subCA2Certificate.getBasicConstraints() != 0) {
  373. getLogger().error("Sub-CA 2 certificate with distinguished name '" +
  374. subCA2Certificate.getSubjectX500Principal().getName() + "' has incorrect value for " +
  375. "pathLenConstraint. Should be 0 instead of " + subCA2Certificate.getBasicConstraints());
  376. return ResponseCodeType.FAILED_CERTIFICATE_EXPIRED;
  377. }
  378. if (subCA1Certificate != null && subCA1Certificate.getBasicConstraints() != 1) {
  379. getLogger().error("Sub-CA 1 certificate with distinguished name '" +
  380. subCA1Certificate.getSubjectX500Principal().getName() + "' has incorrect value for " +
  381. "pathLenConstraint. Should be 1 instead of " + subCA2Certificate.getBasicConstraints());
  382. return ResponseCodeType.FAILED_CERTIFICATE_EXPIRED;
  383. }
  384. responseCode = verifyLeafCertificateAttributes(leafCertificate, pki);
  385. if (responseCode.equals(ResponseCodeType.OK))
  386. return responseCode;
  387. return ResponseCodeType.OK;
  388. }
  389. /**
  390. * Checks certificate attributes for a given leaf certificate belonging to an ISO 15118 PKI.
  391. *
  392. * @param certificate The X.509 certificate whose attributes need to be checked
  393. * @param pki The PKI to which the certificate belongs
  394. * @return
  395. */
  396. public static ResponseCodeType verifyLeafCertificateAttributes(X509Certificate leafCertificate, PKI pki) {
  397. switch (pki) {
  398. case CPO:
  399. if (!verifyDomainComponent(leafCertificate, "CPO")) {
  400. getLogger().error("SECC leaf certificate with distinguished name '" +
  401. leafCertificate.getSubjectX500Principal().getName() + "' has incorrect value for " +
  402. "domain component. Should be 'CPO'");
  403. return ResponseCodeType.FAILED_CERT_CHAIN_ERROR;
  404. }
  405. break;
  406. case CPS:
  407. if (!verifyDomainComponent(leafCertificate, "CPS")) {
  408. getLogger().error("CPS leaf certificate with distinguished name '" +
  409. leafCertificate.getSubjectX500Principal().getName() + "' has incorrect value for " +
  410. "domain component. Should be 'CPS'");
  411. return ResponseCodeType.FAILED_CERT_CHAIN_ERROR;
  412. }
  413. break;
  414. case MO:
  415. if (!isEMAIDSyntaxValid(leafCertificate)) {
  416. return ResponseCodeType.FAILED_CERT_CHAIN_ERROR;
  417. }
  418. break;
  419. case OEM:
  420. if (!verifyDomainComponent(leafCertificate, "OEM")) {
  421. getLogger().error("OEM provisioning certificate with distinguished name '" +
  422. leafCertificate.getSubjectX500Principal().getName() + "' has incorrect value for " +
  423. "domain component. Should be 'OEM'");
  424. return ResponseCodeType.FAILED_CERT_CHAIN_ERROR;
  425. }
  426. break;
  427. default:
  428. break;
  429. }
  430. return ResponseCodeType.OK;
  431. }
  432. /**
  433. * Verifies that the given certificate was signed using the private key that corresponds to the
  434. * public key of the provided certificate.
  435. *
  436. * @param certificate The X509Certificate which is to be checked
  437. * @param issuingCertificate The X.509 certificate which holds the public key corresponding to the private
  438. * key with which the given certificate should have been signed
  439. * @return True, if the verification was successful, false otherwise
  440. */
  441. public static boolean verifySignature(X509Certificate certificate, X509Certificate issuingCertificate) {
  442. X500Principal subject = certificate.getSubjectX500Principal();
  443. X500Principal expectedIssuerSubject = certificate.getIssuerX500Principal();
  444. X500Principal issuerSubject = issuingCertificate.getSubjectX500Principal();
  445. PublicKey publicKeyForSignature = issuingCertificate.getPublicKey();
  446. try {
  447. certificate.verify(publicKeyForSignature);
  448. return true;
  449. } catch (InvalidKeyException | CertificateException | NoSuchAlgorithmException |
  450. NoSuchProviderException | SignatureException e) {
  451. getLogger().warn("\n"
  452. + "\tSignature verification of certificate having distinguished name \n"
  453. + "\t'" + subject.getName() + "'\n"
  454. + "\twith certificate having distinguished name (the issuer) \n"
  455. + "\t'" + issuerSubject.getName() + "'\n"
  456. + "\tfailed. Expected issuer has distinguished name \n"
  457. + "\t'" + expectedIssuerSubject.getName() + "' (" + e.getClass().getSimpleName() + ")", e);
  458. }
  459. return false;
  460. }
  461. /**
  462. * Iterates over the certificates stored in the truststore to verify the signature of the provided certificate
  463. *
  464. * @param trustStoreFilename The relative path and file name of the truststore
  465. * @param certificate The certificate whose signature needs to be verified
  466. * @return True, if the provided certificate has been signed by one of the certificates in the
  467. * truststore, false otherwise
  468. */
  469. public static boolean verifySignature(X509Certificate certificate, String trustStoreFilename) {
  470. KeyStore trustStore = SecurityUtils.getTrustStore(trustStoreFilename, GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString());
  471. X500Principal expectedIssuer = certificate.getIssuerX500Principal();
  472. try {
  473. Enumeration<String> aliases = trustStore.aliases();
  474. while (aliases.hasMoreElements()) {
  475. X509Certificate rootCA = (X509Certificate) trustStore.getCertificate(aliases.nextElement());
  476. if (rootCA.getSubjectX500Principal().getName().equals(expectedIssuer.getName()) &&
  477. verifySignature(certificate, rootCA)) return true;
  478. }
  479. } catch (KeyStoreException | NullPointerException e) {
  480. getLogger().error(e.getClass().getSimpleName() + " occurred while trying to verify trust " +
  481. "status of certificate with distinguished name '" +
  482. certificate.getSubjectX500Principal().getName() + "' with truststore at " +
  483. "location '" + trustStoreFilename + "'", e);
  484. }
  485. return false;
  486. }
  487. /**
  488. * Returns the leaf certificate from a given certificate chain.
  489. *
  490. * @param certChain The certificate chain given as an array of Certificate instances
  491. * @return The leaf certificate (begin not a CA)
  492. */
  493. public static X509Certificate getLeafCertificate(Certificate[] certChain) {
  494. for (Certificate cert : certChain) {
  495. X509Certificate x509Cert = (X509Certificate) cert;
  496. // Check whether the pathLen constraint is set which indicates if this certificate is a CA
  497. if (x509Cert.getBasicConstraints() == -1) return x509Cert;
  498. }
  499. getLogger().warn("No leaf certificate found in given certificate chain");
  500. return null;
  501. }
  502. /**
  503. * Returns the intermediate certificates (sub CAs) from a given certificate chain.
  504. *
  505. * @param certChain The certificate chain given as an array of Certificate instances
  506. * @return The sub certificates given as a list of byte arrays contained in a SubCertiticatesType instance
  507. */
  508. public static SubCertificatesType getSubCertificates(Certificate[] certChain) {
  509. SubCertificatesType subCertificates = new SubCertificatesType();
  510. for (Certificate cert : certChain) {
  511. X509Certificate x509Cert = (X509Certificate) cert;
  512. // Check whether the pathLen constraint is set which indicates if this certificate is a CA
  513. if (x509Cert.getBasicConstraints() != -1)
  514. try {
  515. subCertificates.getCertificate().add(x509Cert.getEncoded());
  516. } catch (CertificateEncodingException e) {
  517. X500Principal subject = x509Cert.getIssuerX500Principal();
  518. getLogger().error("A CertificateEncodingException occurred while trying to get certificate " +
  519. "with distinguished name '" + subject.getName().toString() + "'", e);
  520. }
  521. }
  522. if (subCertificates.getCertificate().size() == 0) {
  523. getLogger().warn("No intermediate CAs found in given certificate array");
  524. }
  525. return subCertificates;
  526. }
  527. /**
  528. * Returns the list of X509IssuerSerialType instances of the root CAs contained in the truststore.
  529. *
  530. * @param trustStoreFileName The relative path and file name of the truststore
  531. * @param trustStorePassword The password which protects the truststore
  532. * @return The list of X509IssuerSerialType instances of the root CAs
  533. */
  534. public static ListOfRootCertificateIDsType getListOfRootCertificateIDs(
  535. String trustStoreFileName,
  536. String trustStorePassword) {
  537. KeyStore evccTrustStore = getTrustStore(trustStoreFileName, trustStorePassword);
  538. ListOfRootCertificateIDsType rootCertificateIDs = new ListOfRootCertificateIDsType();
  539. X509Certificate cert = null;
  540. try {
  541. Enumeration<String> aliases = evccTrustStore.aliases();
  542. while (aliases.hasMoreElements()) {
  543. cert = (X509Certificate) evccTrustStore.getCertificate(aliases.nextElement());
  544. X509IssuerSerialType serialType = new X509IssuerSerialType();
  545. serialType.setX509IssuerName(cert.getIssuerX500Principal().getName());
  546. serialType.setX509SerialNumber(cert.getSerialNumber());
  547. rootCertificateIDs.getRootCertificateID().add(serialType);
  548. }
  549. } catch (KeyStoreException | NullPointerException e) {
  550. getLogger().error(e.getClass().getSimpleName() + " occurred while trying to get list of " +
  551. "root certificate IDs from truststore at location '" + trustStoreFileName + "'", e);
  552. }
  553. return rootCertificateIDs;
  554. }
  555. /**
  556. * Returns an instance of a X.509 certificate created from its raw byte array
  557. *
  558. * @param certificate The byte array representing a X.509 certificate
  559. * @return The X.509 certificate
  560. */
  561. public static X509Certificate getCertificate(byte[] certificate) {
  562. X509Certificate cert = null;
  563. try {
  564. InputStream in = new ByteArrayInputStream(certificate);
  565. CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
  566. cert = (X509Certificate) certFactory.generateCertificate(in);
  567. } catch (CertificateException e) {
  568. getLogger().error("CertificateException occurred when trying to create X.509 certificate from byte array", e);
  569. }
  570. return cert;
  571. }
  572. /**
  573. * Returns the mobility operator Sub-CA 2 certificate (MOSubCA2 certificate) which can verify the signature of the
  574. * contract certificate from the given keystore. The public key of the MOSub2Certificate is then used to verify
  575. * the signature of sales tariffs.
  576. *
  577. * @param keyStoreFileName The relative path and file name of the keystore
  578. * @return The X.509 mobility operator Sub-CA2 certificate (a certificate from a Sub-CA)
  579. */
  580. public static X509Certificate getMOSubCA2Certificate(String keyStoreFileName) {
  581. KeyStore keystore = getKeyStore(keyStoreFileName, GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString());
  582. X509Certificate moSubCA2Certificate = null;
  583. try {
  584. Certificate[] certChain = keystore.getCertificateChain(GlobalValues.ALIAS_CONTRACT_CERTIFICATE.toString());
  585. X509Certificate contractCertificate = getLeafCertificate(certChain);
  586. SubCertificatesType subCertificates = getSubCertificates(certChain);
  587. for (byte[] certificate : subCertificates.getCertificate()) {
  588. X509Certificate x509Cert = getCertificate(certificate);
  589. if (contractCertificate.getIssuerX500Principal().getName().equals(
  590. x509Cert.getSubjectX500Principal().getName())) {
  591. moSubCA2Certificate = x509Cert;
  592. break;
  593. }
  594. }
  595. } catch (KeyStoreException e) {
  596. getLogger().error("KeyStoreException occurred while trying to get MOSubCA2 certificate");
  597. }
  598. return moSubCA2Certificate;
  599. }
  600. /**
  601. * Returns the ECPublicKey instance from its encoded raw bytes.
  602. * The first byte has the fixed value 0x04 indicating the uncompressed form.
  603. * Therefore, the byte array must be of form: [0x04, x coord of point (32 bytes), y coord of point (32 bytes)]
  604. *
  605. * @param publicKeyBytes The byte array representing the encoded raw bytes of the public key
  606. * @return The ECPublicKey instance
  607. */
  608. public static ECPublicKey getPublicKey(byte[] publicKeyBytes) {
  609. // First we separate x and y of coordinates into separate variables
  610. byte[] x = new byte[32];
  611. byte[] y = new byte[32];
  612. System.arraycopy(publicKeyBytes, 1, x, 0, 32);
  613. System.arraycopy(publicKeyBytes, 33, y, 0, 32);
  614. try {
  615. KeyFactory kf = KeyFactory.getInstance("EC");
  616. AlgorithmParameters parameters = AlgorithmParameters.getInstance("EC");
  617. parameters.init(new ECGenParameterSpec("secp256r1"));
  618. ECParameterSpec ecParameterSpec = parameters.getParameterSpec(ECParameterSpec.class);
  619. ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(x), new BigInteger(y)), ecParameterSpec);
  620. ECPublicKey ecPublicKey = (ECPublicKey) kf.generatePublic(ecPublicKeySpec);
  621. return ecPublicKey;
  622. } catch (NoSuchAlgorithmException | InvalidParameterSpecException | InvalidKeySpecException e) {
  623. getLogger().error(e.getClass().getSimpleName() + " occurred when trying to get public key from raw bytes", e);
  624. return null;
  625. }
  626. }
  627. /**
  628. * Returns the public key part of an elliptic curve Diffie-Hellman keypair
  629. *
  630. * @param ecdhKeyPair The elliptic curve Diffie-Hellman keypair
  631. * @return The respective public key
  632. */
  633. public static DiffieHellmanPublickeyType getDHPublicKey(KeyPair ecdhKeyPair) {
  634. DiffieHellmanPublickeyType dhPublicKey = new DiffieHellmanPublickeyType();
  635. /*
  636. * Experience from the test symposium in San Diego (April 2016):
  637. * The Id element of the signature is not restricted in size by the standard itself. But on embedded
  638. * systems, the memory is very limited which is why we should not use long IDs for the signature reference
  639. * element. A good size would be 3 characters max (like the example in the ISO 15118-2 annex J)
  640. */
  641. dhPublicKey.setId("id1");
  642. byte[] uncompressedDHpublicKey = getUncompressedSubjectPublicKey((ECPublicKey) ecdhKeyPair.getPublic());
  643. getLogger().debug("Created DHpublickey: " + ByteUtils.toHexString(uncompressedDHpublicKey));
  644. dhPublicKey.setValue(uncompressedDHpublicKey);
  645. return dhPublicKey;
  646. }
  647. /**
  648. * Returns the ECPrivateKey instance from its raw bytes. Note that you must provide the "s" value of the
  649. * private key, not e.g. the byte array from reading a PKCS#8 key file.
  650. *
  651. * @param privateKeyBytes The byte array (the "s" value) of the private key
  652. * @return The ECPrivateKey instance
  653. */
  654. public static ECPrivateKey getPrivateKey(byte[] privateKeyBytes) {
  655. try {
  656. AlgorithmParameters parameters = AlgorithmParameters.getInstance("EC");
  657. parameters.init(new ECGenParameterSpec("secp256r1"));
  658. ECParameterSpec ecParameterSpec = parameters.getParameterSpec(ECParameterSpec.class);
  659. ECPrivateKeySpec ecPrivateKeySpec = new ECPrivateKeySpec(new BigInteger(privateKeyBytes), ecParameterSpec);
  660. ECPrivateKey privateKey = (ECPrivateKey) KeyFactory.getInstance("EC").generatePrivate(ecPrivateKeySpec);
  661. return privateKey;
  662. } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidParameterSpecException e) {
  663. getLogger().error(e.getClass().getSimpleName() + " occurred when trying to get private key from raw bytes", e);
  664. return null;
  665. }
  666. }
  667. /**
  668. * Searches the given keystore for the private key. It is assumed that the given keystore holds
  669. * only one private key entry whose alias is not known before, which is the case during certificate
  670. * installation when the SECC uses a PKCS#12 container encapsulating the
  671. * contract certificate, its private key and an optional chain of intermediate CAs.
  672. *
  673. * @param keyStore The PKCS#12 keystore
  674. * @return The private key contained in the given keystore as an ECPrivateKey
  675. */
  676. public static ECPrivateKey getPrivateKey(KeyStore keyStore) {
  677. ECPrivateKey privateKey = null;
  678. try {
  679. Enumeration<String> aliases = keyStore.aliases();
  680. // Only one certificate chain (and therefore alias) should be available
  681. while (aliases.hasMoreElements()) {
  682. privateKey = (ECPrivateKey) keyStore.getKey(
  683. aliases.nextElement(),
  684. GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString().toCharArray());
  685. }
  686. } catch (KeyStoreException | UnrecoverableKeyException | NoSuchAlgorithmException |
  687. NullPointerException e) {
  688. getLogger().error(e.getClass().getSimpleName() + " occurred while trying to get private " +
  689. "key from keystore", e);
  690. }
  691. return privateKey;
  692. }
  693. /**
  694. * Reads the private key from an encrypted PKCS#8 file and returns it as an ECPrivateKey instance.
  695. *
  696. * ----- !! IMPORTANT NOTE!! -----
  697. * The PKCS#8 key file must be encrypted using a PKCS#12 encryption scheme, since JCE parsing of Pbes2Parameters (as defined in PKCS#5)
  698. * is buggy in Java 1.8, see also https://bugs.openjdk.java.net/browse/JDK-8076999. The bug results in an IOException when trying to
  699. * instantiate the EncryptedPrivateKeyInfo class.
  700. *
  701. * The OpenSSL command used to create the DER-encoded and encrypted PKCS#8 file needs to use the 'v1 alg' option, specifying a proper algorithm.
  702. * Example: '-v1 PBE-SHA1-3DES' (see https://www.openssl.org/docs/man1.0.2/man1/openssl-pkcs8.html).
  703. * -----
  704. *
  705. * @param A PKCS#8 (.key) file containing the private key with value "s"
  706. * @return The private key as an ECPrivateKey instance
  707. */
  708. public static ECPrivateKey getPrivateKey(String keyFilePath) {
  709. Path fileLocation = Paths.get(keyFilePath);
  710. byte[] pkcs8ByteArray;
  711. try {
  712. pkcs8ByteArray = Files.readAllBytes(fileLocation);
  713. // Get the password that was used to encrypt the private key
  714. PBEKeySpec password = new PBEKeySpec(GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString().toCharArray());
  715. // Read the ASN.1 structure of the PKCS#8 DER-encoded file
  716. EncryptedPrivateKeyInfo encryptedPrivKeyInfo = new EncryptedPrivateKeyInfo(pkcs8ByteArray);
  717. // Instantiate the key factory which will create the symmetric (secret) key using algorithm that is encoded in the ASN.1 structure
  718. // (see 'v1 alg' in OpenSSL's pkcs8 command) and the given password
  719. SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(encryptedPrivKeyInfo.getAlgName());
  720. // Create the symmetric key from the given password
  721. Key decryptKey = secretKeyFactory.generateSecret(password);
  722. // Extract the PKCS8EncodedKeySpec object from the encrypted data
  723. PKCS8EncodedKeySpec pkcs8PrivKeySpec = encryptedPrivKeyInfo.getKeySpec(decryptKey);
  724. // Generate the EC private key
  725. ECPrivateKey privateKey = (ECPrivateKey) KeyFactory.getInstance("EC").generatePrivate(pkcs8PrivKeySpec);
  726. return privateKey;
  727. } catch (IOException | InvalidKeySpecException | NoSuchAlgorithmException | InvalidKeyException e) {
  728. getLogger().error(e.getClass().getSimpleName() + " occurred while trying to access private key at " +
  729. "location '" + keyFilePath + "'");
  730. e.printStackTrace();
  731. return null;
  732. }
  733. }
  734. /**
  735. * Searches the given keystore for the private key which corresponds to the provided alias.
  736. * Example: In case of the EVCC and during certificate installation, the private key of the
  737. * OEM provisioning certificate is needed. During certificate update, the private key of the
  738. * existing contract certificate is needed.
  739. *
  740. * @param keyStore The keystore of EVCC or SECC
  741. * @param alias The alias of a specific private key entry
  742. * @return The private key corresponding to the respective alias in the given keystore
  743. */
  744. public static ECPrivateKey getPrivateKey(KeyStore keyStore, String alias) {
  745. ECPrivateKey privateKey = null;
  746. try {
  747. privateKey = (ECPrivateKey) keyStore.getKey(
  748. alias,
  749. GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString().toCharArray());
  750. } catch (KeyStoreException | UnrecoverableKeyException | NoSuchAlgorithmException e) {
  751. getLogger().error("The private key from keystore with alias '" + alias +
  752. "' could not be retrieved (" + e.getClass().getSimpleName() + ")", e);
  753. }
  754. return privateKey;
  755. }
  756. /**
  757. * Returns the SecretKey instance from its raw bytes
  758. *
  759. * @param key The byte array representing the symmetric SecretKey instance
  760. * @return The SecretKey instance
  761. */
  762. public static SecretKey getSecretKey(byte[] key) {
  763. SecretKey secretKey = new SecretKeySpec(key, 0, key.length, "DiffieHellman");
  764. return secretKey;
  765. }
  766. /**
  767. * Returns the certificate chain from a PKCS#12 container holding credentials such as private key,
  768. * leaf certificate and zero or more intermediate certificates.
  769. *
  770. * @param pkcs12Resource The PKCS#12 container
  771. * @return The certificate chain
  772. */
  773. public static CertificateChainType getCertificateChain(String pkcs12Resource) {
  774. CertificateChainType certChain = new CertificateChainType();
  775. /*
  776. * For testing purposes, the respective PKCS12 container file has already been put in the
  777. * resources folder. However, when implementing a real interface to a secondary actor's backend,
  778. * the retrieval of a certificate must be done via some other online mechanism.
  779. */
  780. KeyStore contractCertificateKeystore = getPKCS12KeyStore(pkcs12Resource, GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString());
  781. if (contractCertificateKeystore == null) {
  782. getLogger().error("Unable to access certificate chain because no PKCS#12 container found at " +
  783. "location '" + pkcs12Resource + "'");
  784. return null;
  785. }
  786. try {
  787. Enumeration<String> aliases = contractCertificateKeystore.aliases();
  788. Certificate[] tempCertChain = null;
  789. // Only one certificate chain (and therefore alias) should be available
  790. while (aliases.hasMoreElements()) {
  791. tempCertChain = contractCertificateKeystore.getCertificateChain(aliases.nextElement());
  792. certChain.setCertificate(getLeafCertificate(tempCertChain).getEncoded());
  793. certChain.setSubCertificates(getSubCertificates(tempCertChain));
  794. }
  795. } catch (KeyStoreException | CertificateEncodingException | NullPointerException e) {
  796. getLogger().error(e.getClass().getSimpleName() + " occurred while trying to get " +
  797. "certificate chain from resource '" + pkcs12Resource + "'", e);
  798. }
  799. return certChain;
  800. }
  801. /**
  802. * Returns the SignedInfo element of the V2GMessage header, based on the provided HashMap which holds
  803. * the reference IDs (URIs) and the corresponding SHA-256 digests.
  804. *
  805. * @param xmlSignatureRefElements A HashMap of Strings (reflecting the reference IDs) and digest values
  806. * @return The SignedInfoType instance
  807. */
  808. public static SignedInfoType getSignedInfo(HashMap<String, byte[]> xmlSignatureRefElements) {
  809. /*
  810. * According to requirement [V2G2-771] in ISO/IEC 15118-2 the following message elements of the
  811. * XML signature framework shall not be used:
  812. * - Id (attribute in SignedInfo)
  813. * - ##any in SignedInfo – CanonicalizationMethod
  814. * - HMACOutputLength in SignedInfo – SignatureMethod
  815. * - ##other in SignedInfo – SignatureMethod
  816. * - Type (attribute in SignedInfo-Reference)
  817. * - ##other in SignedInfo – Reference – Transforms – Transform
  818. * - XPath in SignedInfo – Reference – Transforms – Transform
  819. * - ##other in SignedInfo – Reference – DigestMethod
  820. * - Id (attribute in SignatureValue)
  821. * - Object (in Signature)
  822. * - KeyInfo
  823. */
  824. DigestMethodType digestMethod = new DigestMethodType();
  825. digestMethod.setAlgorithm("http://www.w3.org/2001/04/xmlenc#sha256");
  826. TransformType transform = new TransformType();
  827. transform.setAlgorithm("http://www.w3.org/TR/canonical-exi/");
  828. TransformsType transforms = new TransformsType();
  829. transforms.getTransform().add(transform);
  830. List<ReferenceType> references = new ArrayList<ReferenceType>();
  831. xmlSignatureRefElements.forEach( (k,v) -> {
  832. ReferenceType reference = new ReferenceType();
  833. reference.setDigestMethod(digestMethod);
  834. reference.setDigestValue(v);
  835. reference.setTransforms(transforms);
  836. reference.setURI("#" + k);
  837. references.add(reference);
  838. });
  839. CanonicalizationMethodType canonicalizationMethod = new CanonicalizationMethodType();
  840. canonicalizationMethod.setAlgorithm("http://www.w3.org/TR/canonical-exi/");
  841. SignatureMethodType signatureMethod = new SignatureMethodType();
  842. signatureMethod.setAlgorithm("http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256");
  843. SignedInfoType signedInfo = new SignedInfoType();
  844. signedInfo.setCanonicalizationMethod(canonicalizationMethod);
  845. signedInfo.setSignatureMethod(signatureMethod);
  846. signedInfo.getReference().addAll(references);
  847. return signedInfo;
  848. }
  849. /**
  850. * Saves the newly received contract certificate chain, provided by CertificateInstallationRes or
  851. * CertificateUpdateRes.
  852. *
  853. * @param keyStorePassword The password which protects the EVCC keystore
  854. * @param contractCertChain The certificate chain belonging to the contract certificate
  855. * @param contractCertPrivateKey The private key corresponding to the public key of the leaf certificate
  856. * stored in the certificate chain
  857. * @return True, if the contract certificate chain and private key could be saved, false otherwise
  858. */
  859. public static boolean saveContractCertificateChain(
  860. String keyStorePassword,
  861. CertificateChainType contractCertChain,
  862. ECPrivateKey contractCertPrivateKey) {
  863. KeyStore keyStore = getKeyStore(GlobalValues.EVCC_KEYSTORE_FILEPATH.toString(), keyStorePassword);
  864. try {
  865. if (isPrivateKeyValid(contractCertPrivateKey, contractCertChain)) {
  866. keyStore.setKeyEntry(
  867. GlobalValues.ALIAS_CONTRACT_CERTIFICATE.toString(),
  868. contractCertPrivateKey,
  869. keyStorePassword.toCharArray(),
  870. getCertificateChain(contractCertChain));
  871. // Save the keystore persistently
  872. FileOutputStream fos = new FileOutputStream("evccKeystore.jks");
  873. keyStore.store(fos, GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString().toCharArray());
  874. fos.close();
  875. X509Certificate contractCert = getCertificate(contractCertChain.getCertificate());
  876. getLogger().info("Contract certificate with distinguished name '" +
  877. contractCert.getSubjectX500Principal().getName() + "' saved. " +
  878. "Valid until " + contractCert.getNotAfter()
  879. );
  880. getLogger().debug("Decrypted private key belonging to contract certificate saved. Key bytes: " +
  881. ByteUtils.toHexString(contractCertPrivateKey.getEncoded()));
  882. } else {
  883. getLogger().error("Private key for contract certificate is not valid");
  884. return false;
  885. }
  886. } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException | NullPointerException e) {
  887. getLogger().error(e.getClass().getSimpleName() + " occurred while trying to save contract " +
  888. "certificate chain", e);
  889. return false;
  890. }
  891. return true;
  892. }
  893. /**
  894. * Checks if the private key is a valid key (according to requirement [V2G2-823]) for the received contract
  895. * certificate before saving it to the keystore.
  896. * @param privateKey The private key corresponding to the contract certificate
  897. * @param contractCertChain The received contract certificate chain
  898. * @return True, if the private key is a valid key, false otherwise.
  899. */
  900. private static boolean isPrivateKeyValid(ECPrivateKey privateKey, CertificateChainType contractCertChain) {
  901. AlgorithmParameters parameters;
  902. try {
  903. parameters = AlgorithmParameters.getInstance("EC");
  904. parameters.init(new ECGenParameterSpec("secp256r1"));
  905. ECParameterSpec ecParameterSpec = parameters.getParameterSpec(ECParameterSpec.class);
  906. // Now we need to check if the private key is correct (see requirement [V2G2-823])
  907. BigInteger order = ecParameterSpec.getOrder();
  908. ECPoint basePoint = ecParameterSpec.getGenerator();
  909. BigInteger privateKeyValue = privateKey.getS();
  910. X509Certificate contractCert = getCertificate(contractCertChain.getCertificate());
  911. ECPublicKey publicKey = (ECPublicKey) contractCert.getPublicKey();
  912. // 1. check
  913. if (privateKeyValue.compareTo(order) != -1) {
  914. getLogger().error("Validation of private key failed: its value is not strictly smaller than the "
  915. + "order of the base point");
  916. return false;
  917. }
  918. // 2. check
  919. /*
  920. * TODO:
  921. * No idea how to check for
  922. * "multiplication of the base point with this value must generate a key matching the public key of
  923. * the contract certificate"
  924. * "this value" = value of private key
  925. * -> some more expert knowledge on the arithmetic of elliptic curves is needed to tackle this!
  926. */
  927. } catch (NoSuchAlgorithmException | InvalidParameterSpecException e) {
  928. getLogger().error(e.getClass().getSimpleName() + " occurred when trying to get private key from raw bytes", e);
  929. return false;
  930. }
  931. return true;
  932. }
  933. /**
  934. * Gets the contract certificate from the EVCC keystore.
  935. *
  936. * @return The contract certificate if present, null otherwise
  937. */
  938. public static X509Certificate getContractCertificate() {
  939. X509Certificate contractCertificate = null;
  940. KeyStore evccKeyStore = getKeyStore(
  941. GlobalValues.EVCC_KEYSTORE_FILEPATH.toString(),
  942. GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString()
  943. );
  944. try {
  945. contractCertificate = (X509Certificate) evccKeyStore.getCertificate(GlobalValues.ALIAS_CONTRACT_CERTIFICATE.toString());
  946. } catch (KeyStoreException e) {
  947. getLogger().error("KeyStoreException occurred while trying to get contract certificate from keystore", e);
  948. }
  949. return contractCertificate;
  950. }
  951. /**
  952. * A convenience function which checks if a contract certificate installation is needed.
  953. * Normally not needed because of function getContractCertificateStatus().
  954. *
  955. * @return True, if no contract certificate is store or if the stored certificate is not valid, false otherwise
  956. */
  957. public static boolean isContractCertificateInstallationNeeded() {
  958. X509Certificate contractCert = getContractCertificate();
  959. if (contractCert == null) {
  960. getLogger().info("No contract certificate stored");
  961. return true;
  962. } else if (!verifyValidityPeriod(contractCert).equals(ResponseCodeType.OK)) {
  963. return true;
  964. } else return false;
  965. }
  966. /**
  967. * A convenience function which checks if a contract certificate update is needed.
  968. * Normally not needed because of function getContractCertificateStatus().
  969. *
  970. * @return True, if contract certificate is still valid but about to expire, false otherwise.
  971. * The expiration period is given in GlobalValues.CERTIFICATE_EXPIRES_SOON_PERIOD.
  972. */
  973. public static boolean isContractCertificateUpdateNeeded() {
  974. X509Certificate contractCert = getContractCertificate();
  975. short validityOfContractCert = getValidityPeriod(contractCert);
  976. if (validityOfContractCert < 0) {
  977. getLogger().warn("Contract certificate with distinguished name '" +
  978. contractCert.getSubjectX500Principal().getName() + "' is not valid any more, expired " +
  979. Math.abs(validityOfContractCert) + " days ago");
  980. return false;
  981. } else if (validityOfContractCert <= GlobalValues.CERTIFICATE_EXPIRES_SOON_PERIOD.getShortValue()) {
  982. getLogger().info("Contract certificate with distinguished name '" +
  983. contractCert.getSubjectX500Principal().getName() + "' is about to expire in " +
  984. validityOfContractCert + " days");
  985. return true;
  986. } else return false;
  987. }
  988. /**
  989. * Checks whether a contract certificate
  990. * - is stored
  991. * - in case it is stored, if it is valid
  992. * - in case it is valid, if it expires soon
  993. *
  994. * This method is intended to reduce cryptographic computation overhead by checking both, if installation or
  995. * update is needed, at the same time. When executing either method by itself (isContractCertificateUpdateNeeded() and
  996. * isContractCertificateInstallationNeeded()), each time the certificate is read anew from the Java keystore
  997. * holding the contract certificate. With this method the contract certificate is read just once from the keystore.
  998. *
  999. * @return An enumeration value ContractCertificateStatus (either UPDATE_NEEDED, INSTALLATION_NEEDED, or OK)
  1000. */
  1001. public static ContractCertificateStatus getContractCertificateStatus() {
  1002. X509Certificate contractCert = getContractCertificate();
  1003. if (contractCert == null) {
  1004. getLogger().info("No contract certificate stored");
  1005. return ContractCertificateStatus.INSTALLATION_NEEDED;
  1006. } else if (contractCert != null && !verifyValidityPeriod(contractCert).equals(ResponseCodeType.OK)) {
  1007. return ContractCertificateStatus.INSTALLATION_NEEDED;
  1008. } else {
  1009. short validityOfContractCert = getValidityPeriod(contractCert);
  1010. // Checking for a negative value of validityOfContractCert is not needed because the method
  1011. // isCertificateValid() already checks for that
  1012. if (validityOfContractCert <= GlobalValues.CERTIFICATE_EXPIRES_SOON_PERIOD.getShortValue()) {
  1013. getLogger().info("Contract certificate with distinguished name '" +
  1014. contractCert.getSubjectX500Principal().getName() + "' is about to expire in " +
  1015. validityOfContractCert + " days");
  1016. return ContractCertificateStatus.UPDATE_NEEDED;
  1017. }
  1018. return ContractCertificateStatus.OK;
  1019. }
  1020. }
  1021. /**
  1022. * Returns a list of certificates from the given CertificateChainType with the leaf certificate
  1023. * being the first element and potential subcertificates (intermediate CA certificatess)
  1024. * in the array of certificates.
  1025. *
  1026. * @param certChainType The CertificateChainType instance which holds a leaf certificate and
  1027. * possible intermediate certificates to verify the leaf certificate up to
  1028. * some root certificate.
  1029. * @return An array of Certificates
  1030. */
  1031. public static Certificate[] getCertificateChain(CertificateChainType certChainType) {
  1032. List<byte[]> subCertificates = certChainType.getSubCertificates().getCertificate();
  1033. Certificate[] certChain = new Certificate[subCertificates.size() + 1];
  1034. certChain[0] = getCertificate(certChainType.getCertificate());
  1035. for (int i = 0; i < subCertificates.size(); i++) {
  1036. certChain[i+1] = getCertificate(subCertificates.get(i));
  1037. }
  1038. return certChain;
  1039. }
  1040. /**
  1041. * Generates an elliptic curve key pair using the named curve "secp256r1".
  1042. * This function is mainly used for the ECDH procedure.
  1043. *
  1044. * To use ECC (elliptic curve cryptography), SECC as well as EVCC must agree on all the elements
  1045. * defining the elliptic curve, that is, the "domain parameters" of the scheme. Such domain
  1046. * parameters are predefined by standardization bodies and are commonly known as "standard curves"
  1047. * or "named curves"; a named curve can be referenced either by name or by the unique object
  1048. * identifier defined in the standard documents. For the ISO/IEC 15118-2 document, the named curve
  1049. * "secp256r1" (SECG notation, see http://www.secg.org/sec2-v2.pdf) is used.
  1050. * See [V2G2-818] in ISO/IEC 15118-2 for further information.
  1051. *
  1052. * @return An elliptic curve key pair according to the named curve 'secp256r1'
  1053. */
  1054. public static KeyPair getECKeyPair() {
  1055. KeyPair keyPair = null;
  1056. try {
  1057. KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC");
  1058. ECGenParameterSpec ecParameterSpec = new ECGenParameterSpec("secp256r1");
  1059. keyPairGenerator.initialize(ecParameterSpec, new SecureRandom());
  1060. keyPair = keyPairGenerator.generateKeyPair();
  1061. } catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException e) {
  1062. getLogger().error(e.getClass().getSimpleName() + " occurred while trying to generate ECDH key pair", e);
  1063. }
  1064. return keyPair;
  1065. }
  1066. /**
  1067. * The shared secret is computed using the domain parameters of the named curve "secp256r1", the private key
  1068. * part of the ephemeral key pair, and the OEM provisioning certiicate’s public key (in case of certificate
  1069. * installation) or the contract certificate's public key (in case of certificate update).
  1070. * The shared secret is used as input to a key derivation function.
  1071. * A key derivation function (KDF) is a deterministic algorithm to derive a key of a given
  1072. * size from some secret value. If two parties use the same shared secret value and the same KDF,
  1073. * they should always derive exactly the same key.
  1074. *
  1075. * @param privateKey The private key of an EC key pair generated from the named curve "secp256r1".
  1076. *
  1077. * The mobility operator (MO) provides his ephemeral private key when using this function for
  1078. * generating the shared secret to encrypt the private key of the contract certificate.
  1079. *
  1080. * The EVCC provides the private key belonging to his OEM provisioning certificate's public key
  1081. * when using this function for generating the shared secret to decrypt the encrypted private key
  1082. * of the newly to be installed contract certificate.
  1083. * @param publicKey The public key of an EC key pair generated from the named curve "secp256r1"
  1084. *
  1085. * The mobility operator (MO) provides the static OEM provisioning certificate's (in case of
  1086. * CertificateInstallation) or old contract certificate's (in case of CertificateUpdate)
  1087. * public key when using this function for generating the shared secret to encrypt the private
  1088. * key of the contract certificate.
  1089. *
  1090. * The EVCC provides the ephemeral public key of the MO (coming with the CertificateInstallationRes
  1091. * or CertificateUpdateRes, respectively) when using this function for generating the shared secret
  1092. * to decrypt the encrypted private key of the newly to be installed contract certificate.
  1093. * @return The computed shared secret of the elliptic curve Diffie-Hellman key exchange protocol
  1094. */
  1095. public static byte[] generateSharedSecret(ECPrivateKey privateKey, ECPublicKey publicKey) {
  1096. try {
  1097. KeyAgreement keyAgreement = KeyAgreement.getInstance("ECDH");
  1098. keyAgreement.init(privateKey, new SecureRandom());
  1099. keyAgreement.doPhase(publicKey, true);
  1100. return keyAgreement.generateSecret();
  1101. } catch (InvalidKeyException | NoSuchAlgorithmException e) {
  1102. getLogger().error(e.getClass().getSimpleName() + " occurred while trying to generate the shared secret (ECDH)", e);
  1103. return null;
  1104. }
  1105. }
  1106. /**
  1107. * The key derivation function (KDF). See [V2G2-818] in ISO/IEC 15118-2 for further information.
  1108. *
  1109. * @param sharedSecret The shared secret derived from the ECDH algorithm
  1110. */
  1111. public static SecretKey generateSessionKey(byte[] sharedSecret) {
  1112. MessageDigest md = null;
  1113. /*
  1114. * TODO it is unclear to me what should be the content of suppPubInfo or suppPrivInfo
  1115. * according to page 49 of http://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Ar2.pdf
  1116. * Requirement [V2G2-818] is not clear about that.
  1117. */
  1118. byte[] suppPubInfo = null;
  1119. byte[] suppPrivInfo = null;
  1120. try {
  1121. md = MessageDigest.getInstance("SHA-256");
  1122. } catch (NoSuchAlgorithmException e1) {
  1123. getLogger().error("Message digest algorithm SHA-256 not supported");
  1124. return null;
  1125. }
  1126. ByteArrayOutputStream baosOtherInfo = new ByteArrayOutputStream();
  1127. try {
  1128. baosOtherInfo.write(ByteUtils.toByteArrayFromHexString("01")); // algorithm ID
  1129. baosOtherInfo.write(ByteUtils.toByteArrayFromHexString("55")); // partyUInfo
  1130. baosOtherInfo.write(ByteUtils.toByteArrayFromHexString("56")); // partyVInfo
  1131. if (suppPubInfo != null) baosOtherInfo.write(suppPubInfo);
  1132. if (suppPrivInfo != null) baosOtherInfo.write(suppPrivInfo);
  1133. } catch (IOException e) {
  1134. getLogger().error("IOException occurred while trying to write OtherInfo for session key generation", e);
  1135. }
  1136. byte[] otherInfo = baosOtherInfo.toByteArray();
  1137. // A symmetric encryption key of exactly 128 bits shall be derived.
  1138. byte[] sessionKeyAsByteArray = concatKDF(md, sharedSecret, 128, otherInfo);
  1139. SecretKey sessionKey = null;
  1140. try {
  1141. sessionKey = new SecretKeySpec(sessionKeyAsByteArray, "AES");
  1142. } catch (IllegalArgumentException e) {
  1143. getLogger().error("IllegalArgumentException occurred while trying to generate session key", e);
  1144. }
  1145. return sessionKey;
  1146. }
  1147. /**
  1148. * Implementation of Concatenation Key Derivation Function
  1149. * http://csrc.nist.gov/publications/nistpubs/800-56A/SP800-56A_Revision1_Mar08-2007.pdf
  1150. *
  1151. * Author: NimbusDS Lai Xin Chu and Vladimir Dzhuvinov
  1152. *
  1153. * See https://code.google.com/p/openinfocard/source/browse/trunk/testsrc/org/xmldap/crypto/ConcatKeyDerivationFunction.java?r=770
  1154. */
  1155. private static byte[] concatKDF(MessageDigest md, byte[] z, int keyDataLen, byte[] otherInfo) {
  1156. final long MAX_HASH_INPUTLEN = Long.MAX_VALUE;
  1157. final long UNSIGNED_INT_MAX_VALUE = 4294967295L;
  1158. keyDataLen = keyDataLen/8;
  1159. byte[] key = new byte[keyDataLen];
  1160. int hashLen = md.getDigestLength();
  1161. int reps = keyDataLen / hashLen;
  1162. if (reps > UNSIGNED_INT_MAX_VALUE) {
  1163. getLogger().error("Key derivation failed");
  1164. return null;
  1165. }
  1166. int counter = 1;
  1167. byte[] counterInBytes = ByteUtils.intToFourBytes(counter);
  1168. if ((counterInBytes.length + z.length + otherInfo.length) * 8 > MAX_HASH_INPUTLEN) {
  1169. getLogger().error("Key derivation failed");
  1170. return null;
  1171. }
  1172. for (int i = 0; i <= reps; i++) {
  1173. md.reset();
  1174. md.update(ByteUtils.intToFourBytes(i+1));
  1175. md.update(z);
  1176. md.update(otherInfo);
  1177. byte[] hash = md.digest();
  1178. if (i < reps) {
  1179. System.arraycopy(hash, 0, key, hashLen * i, hashLen);
  1180. } else {
  1181. if (keyDataLen % hashLen == 0) {
  1182. System.arraycopy(hash, 0, key, hashLen * i, hashLen);
  1183. } else {
  1184. System.arraycopy(hash, 0, key, hashLen * i, keyDataLen % hashLen);
  1185. }
  1186. }
  1187. }
  1188. return key;
  1189. }
  1190. private static ContractSignatureEncryptedPrivateKeyType getContractSignatureEncryptedPrivateKey(
  1191. SecretKey sessionKey, ECPrivateKey contractCertPrivateKey) {
  1192. ContractSignatureEncryptedPrivateKeyType encryptedPrivateKey = new ContractSignatureEncryptedPrivateKeyType();
  1193. encryptedPrivateKey.setValue(encryptPrivateKey(sessionKey, contractCertPrivateKey));
  1194. return encryptedPrivateKey;
  1195. }
  1196. /**
  1197. * Encrypts the private key of the contract certificate which is to be sent to the EVCC. First, the
  1198. * shared secret based on the ECDH parameters is calculated, then the symmetric session key with which
  1199. * the private key of the contract certificate is to be encrypted.
  1200. *
  1201. * @param certificateECPublicKey The public key of either the OEM provisioning certificate (in case of
  1202. * CertificateInstallation) or the to be updated contract certificate
  1203. * (in case of CertificateUpdate)
  1204. * @param dhPrivateKey The DH private key
  1205. * @param contractCertPrivateKey The private key of the contract certificate
  1206. * @return The encrypted private key of the to be installed contract certificate
  1207. */
  1208. public static ContractSignatureEncryptedPrivateKeyType encryptContractCertPrivateKey(
  1209. ECPublicKey certificateECPublicKey,
  1210. ECPrivateKey dhPrivateKey,
  1211. ECPrivateKey contractCertPrivateKey) {
  1212. // Generate the shared secret by using the public key of either OEMProvCert or ContractCert
  1213. byte[] sharedSecret = generateSharedSecret(dhPrivateKey, certificateECPublicKey);
  1214. if (sharedSecret == null) {
  1215. getLogger().error("Shared secret could not be generated");
  1216. return null;
  1217. }
  1218. // The session key is generated using the computed shared secret
  1219. SecretKey sessionKey = generateSessionKey(sharedSecret);
  1220. // Finally, the private key of the contract certificate is encrypted using the session key
  1221. ContractSignatureEncryptedPrivateKeyType encryptedContractCertPrivateKey =
  1222. getContractSignatureEncryptedPrivateKey(sessionKey, contractCertPrivateKey);
  1223. return encryptedContractCertPrivateKey;
  1224. }
  1225. /**
  1226. * Applies the algorithm AES-CBC-128 according to NIST Special Publication 800-38A.
  1227. * The initialization vector IV shall be randomly generated before encryption and shall have a
  1228. * length of 128 bit and never be reused.
  1229. * The IV shall be transmitted in the 16 most significant bytes of the
  1230. * ContractSignatureEncryptedPrivateKey field.
  1231. *
  1232. * @param sessionKey The symmetric session key with which the private key will be encrypted
  1233. * @param contractCertPrivateKey The private key which is to be encrypted
  1234. * @return The encrypted private key of the contract certificate given as a byte array
  1235. */
  1236. private static byte[] encryptPrivateKey(SecretKey sessionKey, ECPrivateKey contractCertPrivateKey) {
  1237. try {
  1238. /*
  1239. * Padding of the plain text (private key) is not required as its length (256 bit) is a
  1240. * multiple of the block size (128 bit) of the used encryption algorithm (AES)
  1241. */
  1242. Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
  1243. IvParameterSpec ivParamSpec = new IvParameterSpec(generateRandomNumber(16));
  1244. cipher.init(Cipher.ENCRYPT_MODE, sessionKey, ivParamSpec);
  1245. /*
  1246. * Not the complete ECPrivateKey container, but the private value s represents the 256 bit
  1247. * private key which must be encoded.
  1248. * The private key is stored as an ASN.1 integer which may need to have zero padding
  1249. * in the most significant bits removed (if 33 bytes)
  1250. */
  1251. byte[] encryptedKey;
  1252. if (contractCertPrivateKey.getS().toByteArray().length == 33) {
  1253. byte[] temp = new byte[32];
  1254. System.arraycopy(contractCertPrivateKey.getS().toByteArray(), 1, temp, 0, contractCertPrivateKey.getS().toByteArray().length-1);
  1255. encryptedKey = cipher.doFinal(temp);
  1256. } else {
  1257. encryptedKey = cipher.doFinal(contractCertPrivateKey.getS().toByteArray());
  1258. }
  1259. /*
  1260. * The IV must be transmitted in the 16 most significant bytes of the
  1261. * ContractSignatureEncryptedPrivateKey
  1262. */
  1263. byte[] encryptedKeyWithIV = new byte[ivParamSpec.getIV().length + encryptedKey.length];
  1264. System.arraycopy(ivParamSpec.getIV(), 0, encryptedKeyWithIV, 0, ivParamSpec.getIV().length);
  1265. System.arraycopy(encryptedKey, 0, encryptedKeyWithIV, ivParamSpec.getIV().length, encryptedKey.length);
  1266. getLogger().debug("Encrypted private key: " + ByteUtils.toHexString(encryptedKeyWithIV));
  1267. return encryptedKeyWithIV;
  1268. } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException |
  1269. InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
  1270. getLogger().error(e.getClass().getSimpleName() + " occurred while trying to encrypt private key." +
  1271. "\nSession key (" + sessionKey.getEncoded().length + " bytes): " +
  1272. ByteUtils.toHexString(sessionKey.getEncoded()) +
  1273. "\nContract certificate private key (" + contractCertPrivateKey.getS().toByteArray().length + " bytes): " +
  1274. ByteUtils.toHexString(contractCertPrivateKey.getS().toByteArray()), e);
  1275. }
  1276. return null;
  1277. }
  1278. /**
  1279. * Decrypts the encrypted private key of the contract certificate which is to be installed.
  1280. *
  1281. * @param dhPublicKey The ECDH public key received the the respective response message
  1282. * (either CertificateInstallationRes or CertificateUpdateRes)
  1283. * @param contractSignatureEncryptedPrivateKey The encrypted private key of the contract certificate
  1284. * @param certificateECPrivateKey The private key of either OEMProvisioningCertificate (in case of
  1285. * receipt of CertificateInstallationRes) or the existing ContractCertificate which is to be
  1286. * updated (in case of receipt of CertificateUpdateRes).
  1287. * @return The decrypted private key of the contract certificate which is to be installed
  1288. */
  1289. public static ECPrivateKey decryptContractCertPrivateKey(
  1290. byte[] dhPublicKey,
  1291. byte[] contractSignatureEncryptedPrivateKey,
  1292. ECPrivateKey certificateECPrivateKey) {
  1293. // Generate shared secret
  1294. ECPublicKey publicKey = getPublicKey(dhPublicKey);
  1295. byte[] sharedSecret = generateSharedSecret(certificateECPrivateKey, publicKey);
  1296. if (sharedSecret == null) {
  1297. getLogger().error("Shared secret could not be generated");
  1298. return null;
  1299. }
  1300. // Generate the session key ...
  1301. SecretKey sessionKey = generateSessionKey(sharedSecret);
  1302. if (sessionKey == null) {
  1303. getLogger().error("Session key secret could not be generated");
  1304. return null;
  1305. }
  1306. // ... to decrypt the contract certificate private key
  1307. ECPrivateKey contractCertPrivateKey = decryptPrivateKey(sessionKey, contractSignatureEncryptedPrivateKey);
  1308. if (contractCertPrivateKey == null) {
  1309. getLogger().error("Contract certificate private key secret could not be decrypted");
  1310. return null;
  1311. }
  1312. return contractCertPrivateKey;
  1313. }
  1314. /**
  1315. * The private key corresponding to the contract certificate is to be decrypted by
  1316. * the receiver (EVCC) using the session key derived in the ECDH protocol.
  1317. * Applies the algorithm AES-CBC-128 according to NIST Special Publication 800-38A.
  1318. * The initialization vector IV shall be read from the 16 most significant bytes of the
  1319. * ContractSignatureEncryptedPrivateKey field.
  1320. *
  1321. * @param sessionKey The symmetric session key with which the encrypted private key is to be decrypted
  1322. * @param encryptedKeyWithIV The encrypted private key of the contract certificate given as a byte array
  1323. * whose first 16 byte hold the initialization vector
  1324. * @return The decrypted private key of the contract certificate
  1325. */
  1326. private static ECPrivateKey decryptPrivateKey(SecretKey sessionKey, byte[] encryptedKeyWithIV) {
  1327. byte[] initVector = new byte[16];
  1328. byte[] encryptedKey = null;
  1329. try {
  1330. // Get the first 16 bytes of the encrypted private key which hold the IV
  1331. encryptedKey = new byte[encryptedKeyWithIV.length - 16];
  1332. System.arraycopy(encryptedKeyWithIV, 0, initVector, 0, 16);
  1333. System.arraycopy(encryptedKeyWithIV, 16, encryptedKey, 0, encryptedKeyWithIV.length - 16);
  1334. IvParameterSpec ivParamSpec = new IvParameterSpec(initVector);
  1335. Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
  1336. /*
  1337. * You must have the Java Cryptography Extension (JCE) Unlimited Strength
  1338. * Jurisdiction Policy Files 8 installed, otherwise this cipher.init call will yield a
  1339. * "java.security.InvalidKeyException: Illegal key size"
  1340. */
  1341. cipher.init(Cipher.DECRYPT_MODE, sessionKey, ivParamSpec);
  1342. byte[] decrypted = cipher.doFinal(encryptedKey);
  1343. return getPrivateKey(decrypted);
  1344. } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException |
  1345. InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException |
  1346. NegativeArraySizeException e) {
  1347. getLogger().error(e.getClass().getSimpleName() + " occurred while trying to decrypt private key" +
  1348. "\nSession key (" + (sessionKey != null ? sessionKey.getEncoded().length : 0) + " bytes): " +
  1349. ByteUtils.toHexString(sessionKey.getEncoded()) +
  1350. "\nEncrypted key (" + (encryptedKey != null ? encryptedKey.length : 0) + " bytes): " +
  1351. ByteUtils.toHexString(encryptedKey) +
  1352. "\nEncrypted key with IV (" + (encryptedKeyWithIV != null ? encryptedKeyWithIV.length : 0) + " bytes): " +
  1353. ByteUtils.toHexString(encryptedKey), e);
  1354. }
  1355. return null;
  1356. }
  1357. /**
  1358. * Useful for debugging purposes when verifying a signature and trying to figure out where it went wrong if
  1359. * a signature verification failed.
  1360. *
  1361. * @return
  1362. */
  1363. // public static byte[] decryptSignature(byte[] signature, ECPublicKey publicKey) {
  1364. //
  1365. // }
  1366. /**
  1367. * Returns the EMAID (e-mobility account identifier) from the contract certificate as part of the contract certificate chain.
  1368. *
  1369. * @param contractCertificateChain The certificate chain holding the contract certificate
  1370. * @return The EMAID
  1371. */
  1372. public static EMAIDType getEMAID(CertificateChainType contractCertificateChain) {
  1373. X509Certificate contractCertificate = getCertificate(contractCertificateChain.getCertificate());
  1374. return getEMAIDFromDistinguishedName(contractCertificate.getSubjectX500Principal().getName());
  1375. }
  1376. /**
  1377. * Returns the EMAID (e-mobility account identifier) from the contract certificate.
  1378. *
  1379. * @param contractCertificate The contract certificate
  1380. * @return The EMAID
  1381. */
  1382. public static EMAIDType getEMAID(X509Certificate contractCertificate) {
  1383. return getEMAIDFromDistinguishedName(contractCertificate.getSubjectX500Principal().getName());
  1384. }
  1385. /**
  1386. * Returns the EMAID (e-mobility account identifier) from the contract certificate.
  1387. *
  1388. * @param keyStorePassword The password which protects the keystore holding the contract certificate
  1389. * @return The EMAID
  1390. */
  1391. public static EMAIDType getEMAID(String keyStorePassword) {
  1392. KeyStore keyStore = getKeyStore(GlobalValues.EVCC_KEYSTORE_FILEPATH.toString(), keyStorePassword);
  1393. try {
  1394. X509Certificate contractCertificate =
  1395. (X509Certificate) keyStore.getCertificate(GlobalValues.ALIAS_CONTRACT_CERTIFICATE.toString());
  1396. if (contractCertificate == null) {
  1397. getLogger().error("No contract certificate with alias '" +
  1398. GlobalValues.ALIAS_CONTRACT_CERTIFICATE.toString() + "' found");
  1399. return null;
  1400. }
  1401. return getEMAIDFromDistinguishedName(contractCertificate.getSubjectX500Principal().getName());
  1402. } catch (KeyStoreException e) {
  1403. getLogger().error("KeyStoreException occurred while trying to get EMAID from keystore", e);
  1404. return null;
  1405. }
  1406. }
  1407. /**
  1408. * Reads the EMAID (e-mobility account identifier) from the distinguished name (DN) of a certificate.
  1409. *
  1410. * @param distinguishedName The distinguished name whose 'CN' component holds the EMAID
  1411. * @return The EMAID
  1412. */
  1413. private static EMAIDType getEMAIDFromDistinguishedName(String distinguishedName) {
  1414. EMAIDType emaid = new EMAIDType();
  1415. LdapName ln = null;
  1416. try {
  1417. ln = new LdapName(distinguishedName);
  1418. } catch (InvalidNameException e) {
  1419. getLogger().error("InvalidNameException occurred while trying to get EMAID from distinguished name", e);
  1420. }
  1421. for(Rdn rdn : ln.getRdns()) {
  1422. if (rdn.getType().equalsIgnoreCase("CN")) {
  1423. // Optional hyphens used for better human readability must be omitted here
  1424. emaid.setId("id1");
  1425. emaid.setValue(rdn.getValue().toString().replace("-", ""));
  1426. break;
  1427. }
  1428. }
  1429. return emaid;
  1430. }
  1431. /**
  1432. * Searches a given keystore either for a contract certificate chain or OEM provisioning certificate
  1433. * chain, determined by the alias (the alias is associated with the certificate chain and the private
  1434. * key).
  1435. * However, it may be the case that more than once contract certificate is installed in the EV,
  1436. * in which case an OEM specific implementation would need to interact at this point with a HMI in
  1437. * order to enable the user to select the certificate which is to be used for contract based charging.
  1438. *
  1439. * @param evccKeyStore The keystore to check for the respective certificate chain
  1440. * @param alias The alias associated with a key entry and certificate chain
  1441. * @return The respective certificate chain if present, null otherwise
  1442. */
  1443. public static CertificateChainType getCertificateChain(KeyStore evccKeyStore, String alias) {
  1444. CertificateChainType certChain = new CertificateChainType();
  1445. SubCertificatesType subCertificates = new SubCertificatesType();
  1446. try {
  1447. Certificate[] certChainArray = evccKeyStore.getCertificateChain(alias);
  1448. if (certChainArray == null) {
  1449. getLogger().info("No certificate chain found for alias '" + alias + "'");
  1450. return null;
  1451. }
  1452. certChain.setCertificate(certChainArray[0].getEncoded());
  1453. for (int i = 1; i < certChainArray.length; i++) {
  1454. subCertificates.getCertificate().add(certChainArray[i].getEncoded());
  1455. }
  1456. certChain.setSubCertificates(subCertificates);
  1457. return certChain;
  1458. } catch (KeyStoreException | CertificateEncodingException e) {
  1459. getLogger().error(e.getClass().getSimpleName() + " occurred while trying to get certificate chain", e);
  1460. return null;
  1461. }
  1462. }
  1463. /**
  1464. * Returns a random number of a given length of bytes.
  1465. *
  1466. * @param lengthOfBytes The number of bytes which hold the generated random number
  1467. * @return A random number given as a byte array
  1468. */
  1469. public static byte[] generateRandomNumber(int lengthOfBytes) {
  1470. // TODO how to assure that the entropy of the genChallenge is at least 120 bits according to [V2G2-826]?
  1471. SecureRandom random = new SecureRandom();
  1472. byte[] randomNumber = new byte[lengthOfBytes];
  1473. random.nextBytes(randomNumber);
  1474. return randomNumber;
  1475. }
  1476. /**
  1477. * Generates a digest for a complete message or field (which ever is handed over as first parameter).
  1478. * During digest (SHA-256) generation, the parameter is converted to a JAXBElement and then EXI encoded
  1479. * using the respective EXI schema-informed grammar. If the digest for the signature is to be generated,
  1480. * the second parameter is to be set to true, for all other messages or fields the second parameter
  1481. * needs to be set to false.
  1482. *
  1483. * @param jaxbMessageOrField The message or field for which a digest is to be generated, given as a JAXB element
  1484. * @param digestForSignedInfoElement True if a digest for the SignedInfoElement of the header's signature is to be generated, false otherwise
  1485. * @return The SHA-256 digest for message or field
  1486. */
  1487. @SuppressWarnings("rawtypes")
  1488. public static byte[] generateDigest(String id, JAXBElement jaxbMessageOrField) {
  1489. byte[] encoded;
  1490. // The schema-informed fragment grammar option needs to be used for EXI encodings in the header's signature
  1491. getExiCodec().setFragment(true);
  1492. /*
  1493. * When creating the signature value for the SignedInfoElement, we need to use the XMLdsig schema,
  1494. * whereas for creating the reference elements of the signature, we need to use the V2G_CI_MsgDef schema.
  1495. */
  1496. if (jaxbMessageOrField.getValue() instanceof SignedInfoType) {
  1497. encoded = getExiCodec().encodeEXI(jaxbMessageOrField, GlobalValues.SCHEMA_PATH_XMLDSIG.toString());
  1498. } else encoded = getExiCodec().encodeEXI(jaxbMessageOrField, GlobalValues.SCHEMA_PATH_MSG_DEF.toString());
  1499. // Do not use the schema-informed fragment grammar option for other EXI encodings (message bodies)
  1500. getExiCodec().setFragment(false);
  1501. if (encoded == null) {
  1502. getLogger().error("Digest could not be generated because of EXI encoding problem");
  1503. return null;
  1504. }
  1505. try {
  1506. MessageDigest md = MessageDigest.getInstance("SHA-256");
  1507. md.update(encoded);
  1508. byte[] digest = md.digest();
  1509. if (showSignatureVerificationLog) {
  1510. /*
  1511. * Show Base64 encoding of digests only for reference elements, not for the SignedInfo element.
  1512. * The hashed SignedInfo element is input for ECDSA before the final signature value gets Base64 encoded.
  1513. */
  1514. if ( !(jaxbMessageOrField.getValue() instanceof SignedInfoType) ) {
  1515. getLogger().debug("\n"
  1516. + "\tDigest generated for XML reference element " + jaxbMessageOrField.getName().getLocalPart() + " with ID '" + id + "': " + ByteUtils.toHexString(digest) + "\n"
  1517. + "\tBase64 encoding of digest: " + Base64.getEncoder().encodeToString(digest));
  1518. }
  1519. }
  1520. return digest;
  1521. } catch (NoSuchAlgorithmException e) {
  1522. getLogger().error("NoSuchAlgorithmException occurred while trying to create digest", e);
  1523. return null;
  1524. }
  1525. }
  1526. /**
  1527. * Signs the SignedInfo element of the V2GMessage header.
  1528. *
  1529. * @param signedInfoElementExi The EXI-encoded SignedInfo element given as a byte array
  1530. * @param ecPrivateKey The private key which is used to sign the SignedInfo element
  1531. * @return The signature value for the SignedInfo element given as a byte array
  1532. */
  1533. public static byte[] signSignedInfoElement(byte[] signedInfoElementExi, ECPrivateKey ecPrivateKey) {
  1534. try {
  1535. Signature ecdsa = Signature.getInstance("SHA256withECDSA", "SunEC");
  1536. getLogger().debug("EXI encoded SignedInfo: " + ByteUtils.toHexString(signedInfoElementExi));
  1537. if (ecPrivateKey != null) {
  1538. getLogger().debug("\n\tPrivate key used for creating signature: " + ByteUtils.toHexString(ecPrivateKey.getS().toByteArray()));
  1539. ecdsa.initSign(ecPrivateKey);
  1540. ecdsa.update(signedInfoElementExi);
  1541. byte[] signature = ecdsa.sign();
  1542. // Java operates on DER encoded signatures, but we must send the raw r and s values as signature
  1543. byte[] rawSignature = getRawSignatureFromDEREncoding(signature);
  1544. getLogger().debug("Signature value: " + ByteUtils.toHexString(rawSignature));
  1545. return rawSignature;
  1546. } else {
  1547. getLogger().error("Private key used to sign SignedInfo element is null");
  1548. return null;
  1549. }
  1550. } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException | NoSuchProviderException e) {
  1551. getLogger().error(e.getClass().getSimpleName() + " occurred while trying to create signature", e);
  1552. return null;
  1553. }
  1554. }
  1555. /**
  1556. * Verifies the signature given in the received header of an EVCC or SECC message
  1557. *
  1558. * @param signature The received header's signature
  1559. * @param jaxbSignature The received header's signature, given as a JAXB element (needed for EXI operations)
  1560. * @param verifyXMLSigRefElements The HashMap of signature IDs and digest values of the message body
  1561. * or fields respectively of the received message (to cross-check against the XML reference
  1562. * elements contained in the received message header)
  1563. * @param verifyCert The certificate holding the public key corresponding to the private key which was used
  1564. * for the signature. Given as a byte array, this function will call verifySignature() with an X509Certificate
  1565. * as last parameter.
  1566. * @return True, if digest validation of all XML reference elements and signature validation was
  1567. * successful, false otherwise
  1568. */
  1569. public static boolean verifySignature(
  1570. SignatureType signature,
  1571. JAXBElement<SignedInfoType> jaxbSignature,
  1572. HashMap<String, byte[]> verifyXMLSigRefElements,
  1573. byte[] verifyCert) {
  1574. X509Certificate x509VerifyCert = getCertificate(verifyCert);
  1575. return verifySignature(signature, jaxbSignature, verifyXMLSigRefElements, x509VerifyCert);
  1576. }
  1577. /**
  1578. * Verifies the signature given in the received header of an EVCC or SECC message
  1579. *
  1580. * @param signature The received header's signature
  1581. * @param jaxbSignature The received header's signature, given as a JAXB element (needed for EXI operations)
  1582. * @param verifyXMLSigRefElements The HashMap of signature IDs and digest values of the message body
  1583. * or fields respectively of the received message (to cross-check against the XML reference
  1584. * elements contained in the received message header)
  1585. * @param verifyCert The certificate holding the public key corresponding to the private key which was used for the signature
  1586. * @return True, if digest validation of all XML reference elements and signature validation was
  1587. * successful, false otherwise
  1588. */
  1589. public static boolean verifySignature(
  1590. SignatureType signature,
  1591. JAXBElement<SignedInfoType> jaxbSignedInfo,
  1592. HashMap<String, byte[]> verifyXMLSigRefElements,
  1593. X509Certificate verifyCert) {
  1594. byte[] calculatedReferenceDigest;
  1595. boolean messageDigestsEqual;
  1596. /*
  1597. * 1. step:
  1598. * Iterate over all element IDs of the message which should have been signed and find the
  1599. * respective Reference element in the given message header
  1600. */
  1601. for (Map.Entry<String, byte[]> verifyXMLSigRefElement : verifyXMLSigRefElements.entrySet()) {
  1602. String id = verifyXMLSigRefElement.getKey();
  1603. getLogger().debug("Verifying digest for element '" + id + "'");
  1604. messageDigestsEqual = false;
  1605. calculatedReferenceDigest = verifyXMLSigRefElement.getValue();
  1606. for (ReferenceType reference : signature.getSignedInfo().getReference()) {
  1607. if (reference == null) {
  1608. getLogger().warn("Reference element to check is null");
  1609. continue;
  1610. }
  1611. // 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!
  1612. if (reference.getURI() == null) {
  1613. getLogger().warn("Reference ID element is null");
  1614. continue;
  1615. }
  1616. if (reference.getURI().equals('#' + id)) {
  1617. messageDigestsEqual = MessageDigest.isEqual(reference.getDigestValue(), calculatedReferenceDigest);
  1618. if (showSignatureVerificationLog) {
  1619. getLogger().debug("\n"
  1620. + "\tReceived digest of reference with ID '" + id + "': " + ByteUtils.toHexString(reference.getDigestValue()) + "\n"
  1621. + "\tCalculated digest of reference with ID '" + id + "': " + ByteUtils.toHexString(calculatedReferenceDigest) + "\n"
  1622. + "\t==> Match: " + messageDigestsEqual);
  1623. }
  1624. }
  1625. }
  1626. if (!messageDigestsEqual) {
  1627. getLogger().error("No matching signature found for ID '" + id + "' and digest value " +
  1628. ByteUtils.toHexString(calculatedReferenceDigest));
  1629. return false;
  1630. }
  1631. }
  1632. /*
  1633. * 2. step:
  1634. * Check the signature itself
  1635. */
  1636. ECPublicKey ecPublicKey = (ECPublicKey) verifyCert.getPublicKey();
  1637. Signature ecdsa;
  1638. boolean verified;
  1639. try {
  1640. getLogger().debug("Verifying signature of SignedInfo element ...");
  1641. // Check if signature verification logging is to be shown (for debug purposes)
  1642. if (showSignatureVerificationLog) showSignatureVerificationLog(verifyCert, signature, jaxbSignedInfo, ecPublicKey);
  1643. ecdsa = Signature.getInstance("SHA256withECDSA");
  1644. // The Signature object needs to be initialized by setting it into the VERIFY state with the public key
  1645. ecdsa.initVerify(ecPublicKey);
  1646. // The data to be signed needs to be supplied to the Signature object
  1647. byte[] exiEncodedSignedInfo = getExiCodec().getExiEncodedSignedInfo(jaxbSignedInfo);
  1648. ecdsa.update(exiEncodedSignedInfo);
  1649. // Java operates on DER encoded signature values, but the sent signature consists of the raw r and s value
  1650. byte[] signatureValue = signature.getSignatureValue().getValue();
  1651. byte[] derEncodedSignatureValue = getDEREncodedSignature(signatureValue);
  1652. // The verify() method will do both, the decryption and SHA256 validation. So don't hash separately before verifying
  1653. verified = ecdsa.verify(derEncodedSignatureValue);
  1654. return verified;
  1655. } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
  1656. getLogger().error(e.getClass().getSimpleName() + " occurred while trying to verify signature value", e);
  1657. return false;
  1658. }
  1659. }
  1660. /**
  1661. * Shows some extended logging while verifying a signature for debugging purposes.
  1662. * @param verifyCert The X509Certificate whose public key is used to verify the signature, used for printing the
  1663. * certificate's subject value
  1664. * @param signature The signature contained in the header of the V2GMessage
  1665. * @param ecPublicKey The public key used to verify the signature
  1666. */
  1667. private static void showSignatureVerificationLog(
  1668. X509Certificate verifyCert,
  1669. SignatureType signature,
  1670. JAXBElement<SignedInfoType> jaxbSignedInfo,
  1671. ECPublicKey ecPublicKey) {
  1672. byte[] computedSignedInfoDigest = generateDigest("", jaxbSignedInfo);
  1673. byte[] receivedSignatureValue = signature.getSignatureValue().getValue();
  1674. getLogger().debug("\n"
  1675. + "\tCertificate used to verify signature: " + verifyCert.getSubjectX500Principal().getName() + "\n"
  1676. + "\tPublic key used to verify signature: " + ByteUtils.toHexString(getUncompressedSubjectPublicKey(ecPublicKey)) + "\n"
  1677. + "\tReceived signature value: " + ByteUtils.toHexString(receivedSignatureValue) + " (Base64: " + Base64.getEncoder().encodeToString(receivedSignatureValue) + ")\n"
  1678. + "\tCalculated digest of SignedInfo element: " + ByteUtils.toHexString(computedSignedInfoDigest));
  1679. }
  1680. /**
  1681. * Java puts some encoding information into the ECPublicKey.getEncoded().
  1682. * This method returns the raw ECPoint (the x and y coordinate of the public key) in uncompressed form
  1683. * (with the 0x04 as first octet), aka the Subject Public Key according to RFC 5480
  1684. *
  1685. * @param ecPublicKey The ECPublicKey provided by Java
  1686. * @return The uncompressed Subject Public Key (with the first octet set to 0x04)
  1687. */
  1688. public static byte[] getUncompressedSubjectPublicKey(ECPublicKey ecPublicKey) {
  1689. byte[] uncompressedPubKey = new byte[65];
  1690. uncompressedPubKey[0] = 0x04;
  1691. byte[] affineX = ecPublicKey.getW().getAffineX().toByteArray();
  1692. byte[] affineY = ecPublicKey.getW().getAffineY().toByteArray();
  1693. // If the length is 33 bytes, then the first byte is a 0x00 which is to be omitted
  1694. if (affineX.length == 33)
  1695. System.arraycopy(affineX, 1, uncompressedPubKey, 1, 32);
  1696. else
  1697. System.arraycopy(affineX, 0, uncompressedPubKey, 1, 32);
  1698. if (affineY.length == 33)
  1699. System.arraycopy(affineY, 1, uncompressedPubKey, 33, 32);
  1700. else
  1701. System.arraycopy(affineY, 0, uncompressedPubKey, 33, 32);
  1702. return uncompressedPubKey;
  1703. }
  1704. /**
  1705. * An ECDSA signature consists of two positive integers r and s, each of the bit length equal to the curve size.
  1706. * When Java is creating an ECDSA signature, it is encoding it in the DER (Distinguished Encoding Rules) format.
  1707. * But in ISO 15118, we do not expect DER encoded signatures. Thus, this function takes the DER encoded signature
  1708. * as input and returns the raw r and s integer values of the signature.
  1709. * See further explanations in the @getDEREncodedSignature function for DER encoded ECDSA signatures.
  1710. *
  1711. * @param derEncodedSignature The DER encoded signature as a result from java.security.Signature.sign()
  1712. * @return A byte array containing only the r and s value of the signature
  1713. */
  1714. public static byte[] getRawSignatureFromDEREncoding(byte[] derEncodedSignature) {
  1715. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  1716. byte[] r = new byte[32];
  1717. byte[] s = new byte[32];
  1718. // Length of r is encoded in the fourth byte
  1719. int lengthOfR = derEncodedSignature[3];
  1720. // Length of r is encoded in the second byte AFTER r
  1721. int lengthOfS = derEncodedSignature[lengthOfR + 5];
  1722. // Length of r and s are either 33 bytes (including padding byte 0x00), 32 bytes (normal), or less (leftmost 0x00 bytes were removed)
  1723. try {
  1724. if (lengthOfR == 33) System.arraycopy(derEncodedSignature, 5, r, 0, lengthOfR - 1); // skip leftmost padding byte 0x00
  1725. else if (lengthOfR == 32) System.arraycopy(derEncodedSignature, 4, r, 0, lengthOfR);
  1726. else System.arraycopy(derEncodedSignature, 4, r, 32 - lengthOfR, lengthOfR); // destPos = number of leftmost 0x00 bytes
  1727. if (lengthOfS == 33) System.arraycopy(derEncodedSignature, lengthOfR + 7, s, 0, lengthOfS - 1); // skip leftmost padding byte 0x00
  1728. else if (lengthOfS == 32) System.arraycopy(derEncodedSignature, lengthOfR + 6, s, 0, lengthOfS);
  1729. else System.arraycopy(derEncodedSignature, lengthOfR + 6, s, 32 - lengthOfS, lengthOfS); // destPos = number of leftmost 0x00 bytes
  1730. } catch (ArrayIndexOutOfBoundsException e) {
  1731. getLogger().error("ArrayIndexOutOfBoundsException occurred while trying to get raw signature from DER encoded signature.", e);
  1732. }
  1733. try {
  1734. baos.write(r);
  1735. baos.write(s);
  1736. } catch (IOException e) {
  1737. getLogger().error("IOException occurred while trying to write r and s into DER-encoded signature", e);
  1738. }
  1739. byte[] rawRAndS = baos.toByteArray();
  1740. if (showSignatureVerificationLog) {
  1741. StringBuilder sb = new StringBuilder();
  1742. sb.append("Signature encoding DER -> raw:").append(System.lineSeparator());
  1743. sb.append("\tDER: ").append(ByteUtils.toHexString(derEncodedSignature)).append(System.lineSeparator());
  1744. sb.append("\tR: ").append(ByteUtils.toHexString(r)).append(System.lineSeparator());
  1745. sb.append("\tS: ").append(ByteUtils.toHexString(s)).append(System.lineSeparator());
  1746. sb.append("\tRaw: ").append(ByteUtils.toHexString(rawRAndS));
  1747. getLogger().debug(sb.toString());
  1748. }
  1749. return rawRAndS;
  1750. }
  1751. /**
  1752. * When encoded in DER, the signature - holding the
  1753. * x-coordinate of the elliptic curve point in the value "r"
  1754. * and the
  1755. * y-coordinate of the elliptic curve point in the value "s"
  1756. * - becomes the following sequence of bytes (in total somewhere between 68 and 72 bytes instead of 64 bytes):
  1757. *
  1758. * 0x30 len(z) 0x02 len(r) r 0x02 len(s) s
  1759. *
  1760. * where:
  1761. *
  1762. * - 0x30: is always the first byte of the DER encoded signature format (ASN.1 tag for sequence)
  1763. *
  1764. * - len(z): is a single byte value, encoding the length in bytes of the sequence z (remaining list of bytes)
  1765. * (from the first 0x02 to the end of the encoding); is a value between 0x43 and 0x46
  1766. *
  1767. * - 0x02: is a fixed value indicating that an integer value will follow (ASN.1 tag for int)
  1768. *
  1769. * - len(r): is a single byte value, encoding the length in bytes of r;
  1770. * Distinguished Encoding Rules (DER)-encoded integers are defined so that they can encode both positive and negative values
  1771. * (aka signed values). This means that the leftmost bit (aka most-significant bit in big-endian) indicates whether the value
  1772. * is positive (0) or negative (1).
  1773. * For ECDSA, however, the r and s values are positive integers. So the leftmost bit must be a 0. If it's not, a 0x00
  1774. * padding byte must be added.
  1775. *
  1776. * Furthermore, DER require that integer values are represented in the shortest byte representation possible. This
  1777. * effectively prohibits the use of leading zeroes (0x00) if the leftmost bit was not set to 1.
  1778. *
  1779. * So len(r) will either be 0x21 (33 bytes), 0x20 (32 bytes) or less (mostly not less than 0x1F (31 bytes)).
  1780. * Case 31 bytes or less: The leftmost bytes of the raw (non-DER-encoded) r are 0x00 and, according to DER, need to be
  1781. * removed so that r is DER-encoded in the shortest possible way. Also, the leftmost bit of the
  1782. * remaining byte array is 0 (-> a positive x-value).
  1783. * Case 32 bytes: What we would normally expect, as the x- and y-coordinates are positive values of 32 bytes length.
  1784. * The leftmost bit is set to 0 and the leftmost byte is not 0x00.
  1785. * Case 33 bytes: A padding 0x00 byte was added as the most significant (leftmost) byte because the raw (non-DER-encoded) r
  1786. * value had the leftmost bit set to 1, which would result in a negative value.
  1787. *
  1788. * - r: is the signed big-endian encoding of the value "r", of minimal length;
  1789. *
  1790. * - 0x02: is a fixed value indicating that an integer value will follow (ASN.1 tag for int)
  1791. *
  1792. * - len(s): is a single byte value, encoding the length in bytes of s;
  1793. * (See further explanation of len(r) that applies as well for len(s))
  1794. *
  1795. * - s: is the signed big-endian encoding of the value "s", of minimal length.
  1796. *
  1797. * @param rawSignatureValue The r and s values (each 32 bytes) of an ECDSA signature, given as a byte array of 64 bytes
  1798. * @return A byte array representing the DER-encoded version of the raw r and s values
  1799. */
  1800. private static byte[] getDEREncodedSignature (byte[] rawSignatureValue) {
  1801. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  1802. // First we separate x and y of coordinates into separate byte arrays r and s
  1803. byte[] r = new byte[32];
  1804. byte[] s = new byte[32];
  1805. try {
  1806. System.arraycopy(rawSignatureValue, 0, r, 0, 32);
  1807. System.arraycopy(rawSignatureValue, 32, s, 0, 32);
  1808. } catch (ArrayIndexOutOfBoundsException e) {
  1809. getLogger().error("ArrayIndexOutOfBoundsException occurred while trying to get DER encoded signature", e);
  1810. return new byte[0];
  1811. }
  1812. // Then encode both parts (r & s) individually
  1813. byte[] rDerEncoded = getDerEncodedSignatureValue(r);
  1814. byte[] sDerEncoded = getDerEncodedSignatureValue(s);
  1815. // And write everything with the proper header to the buffer
  1816. baos.write(0x30);
  1817. baos.write(rDerEncoded.length + sDerEncoded.length);
  1818. try {
  1819. baos.write(rDerEncoded);
  1820. baos.write(sDerEncoded);
  1821. } catch (IOException e) {
  1822. getLogger().error("IOException occurred while trying to write DER encoded signature r and s value", e);
  1823. }
  1824. byte[] derEncodedSignature = baos.toByteArray();
  1825. try {
  1826. baos.close();
  1827. } catch (IOException e) {
  1828. getLogger().error("IOException occurred while trying to close ByteArrayOutputStream", e);
  1829. }
  1830. if (showSignatureVerificationLog) {
  1831. StringBuilder sb = new StringBuilder();
  1832. sb.append("Signature encoding raw -> DER:").append(System.lineSeparator());
  1833. sb.append("\tRaw: ").append(ByteUtils.toHexString(rawSignatureValue)).append(System.lineSeparator());
  1834. sb.append("\tR: ").append(ByteUtils.toHexString(r)).append(System.lineSeparator());
  1835. sb.append("\tR (DER-encoded): ").append(ByteUtils.toHexString(rDerEncoded)).append(System.lineSeparator());
  1836. sb.append("\tS: ").append(ByteUtils.toHexString(s)).append(System.lineSeparator());
  1837. sb.append("\tS (DER-encoded): ").append(ByteUtils.toHexString(sDerEncoded)).append(System.lineSeparator());
  1838. sb.append("\tDER: ").append(ByteUtils.toHexString(derEncodedSignature));
  1839. getLogger().debug(sb.toString());
  1840. }
  1841. return derEncodedSignature;
  1842. }
  1843. /**
  1844. * Helper function which provides a partial DER encoding for positive integer values used for r and s
  1845. *
  1846. * @param value byte array containing a positive integer (non two's complement)
  1847. * @return DER-encoded value of r or s (depending on the @param), including int content type, length and, if needed, padding
  1848. */
  1849. private static byte[] getDerEncodedSignatureValue(byte[] value) {
  1850. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  1851. // Check if the value is negative which is equivalent to r[0] being bigger than 0x7f
  1852. boolean isFillByteNeeded = value[0] < 0;
  1853. int indexOfFirstNonNullValue = 0;
  1854. for (/* empty init statement */; indexOfFirstNonNullValue < value.length; indexOfFirstNonNullValue++) {
  1855. if (value[indexOfFirstNonNullValue] != 0) {
  1856. break;
  1857. }
  1858. }
  1859. byte derEncodedLength = (byte) (value.length - indexOfFirstNonNullValue);
  1860. baos.write(0x02);
  1861. if (isFillByteNeeded) {
  1862. baos.write(derEncodedLength + 1);
  1863. baos.write(0x00);
  1864. } else {
  1865. baos.write(derEncodedLength);
  1866. }
  1867. baos.write(value, indexOfFirstNonNullValue, value.length - indexOfFirstNonNullValue);
  1868. byte[] result = baos.toByteArray();
  1869. try {
  1870. baos.close();
  1871. } catch (IOException e) {
  1872. getLogger().error("IOException occurred while trying to close ByteArrayOutputStream", e);
  1873. }
  1874. return result;
  1875. }
  1876. /**
  1877. * Sets the SSLContext of the TLSServer and TLSClient with the given keystore and truststore locations as
  1878. * well as the password protecting the keystores/truststores.
  1879. *
  1880. * @param keyStorePath The relative path and filename for the keystore
  1881. * @param trustStorePath The relative path and filename for the truststore
  1882. * @param keyStorePassword The password protecting the keystore
  1883. */
  1884. public static void setSSLContext(
  1885. String keyStorePath,
  1886. String trustStorePath,
  1887. String keyStorePassword) {
  1888. KeyStore keyStore = SecurityUtils.getKeyStore(keyStorePath, keyStorePassword);
  1889. KeyStore trustStore = SecurityUtils.getKeyStore(trustStorePath, keyStorePassword);
  1890. try {
  1891. // Initialize a key manager factory with the keystore
  1892. KeyManagerFactory keyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
  1893. keyFactory.init(keyStore, keyStorePassword.toCharArray());
  1894. KeyManager[] keyManagers = keyFactory.getKeyManagers();
  1895. // Initialize a trust manager factory with the truststore
  1896. TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
  1897. trustFactory.init(trustStore);
  1898. TrustManager[] trustManagers = trustFactory.getTrustManagers();
  1899. // Initialize an SSL context to use these managers and set as default
  1900. SSLContext sslContext = SSLContext.getInstance("TLS");
  1901. sslContext.init(keyManagers, trustManagers, null);
  1902. SSLContext.setDefault(sslContext);
  1903. } catch (NoSuchAlgorithmException | UnrecoverableKeyException | KeyStoreException |
  1904. KeyManagementException e) {
  1905. getLogger().error(e.getClass().getSimpleName() + " occurred while trying to initialize SSL context");
  1906. }
  1907. }
  1908. /**
  1909. * Checks the syntax of the EMAID according to Annex H.1 of ISO 15118-2
  1910. *
  1911. * @param certChain The contract certificate chain. The EMAID is read from the contract certificate's common name
  1912. * @return True, if the syntax is valid, false otherwise
  1913. */
  1914. public static boolean isEMAIDSyntaxValid(X509Certificate contractCertificate) {
  1915. String emaid = getEMAID(contractCertificate).getValue().toUpperCase();
  1916. if (emaid.length() < 14 || emaid.length() > 18) {
  1917. getLogger().error("EMAID is invalid. Its length (" + emaid.length() + ") mus be between "
  1918. + "14 (min, excluding separators) and 18 (max, including separators)");
  1919. return false;
  1920. }
  1921. String emaidWithoutSeparator = emaid.replace("-", "");
  1922. // Check country code
  1923. if (Character.isDigit(emaidWithoutSeparator.charAt(0)) || Character.isDigit(emaidWithoutSeparator.charAt(1))) {
  1924. getLogger().error("EMAID (" + emaid + ") is invalid, the first two characters must not be a digit");
  1925. return false;
  1926. }
  1927. // Check provider ID
  1928. if (! (Character.isLetterOrDigit(emaidWithoutSeparator.charAt(2)) &&
  1929. Character.isLetterOrDigit(emaidWithoutSeparator.charAt(3)) &&
  1930. Character.isLetterOrDigit(emaidWithoutSeparator.charAt(4))) ) {
  1931. getLogger().error("EMAID (" + emaid + ") is invalid, the provider ID must be alpha-numerical");
  1932. return false;
  1933. }
  1934. // Check emaInstance
  1935. if (! (Character.isLetterOrDigit(emaidWithoutSeparator.charAt(5)) &&
  1936. Character.isLetterOrDigit(emaidWithoutSeparator.charAt(6)) &&
  1937. Character.isLetterOrDigit(emaidWithoutSeparator.charAt(7)) &&
  1938. Character.isLetterOrDigit(emaidWithoutSeparator.charAt(8)) &&
  1939. Character.isLetterOrDigit(emaidWithoutSeparator.charAt(9)) &&
  1940. Character.isLetterOrDigit(emaidWithoutSeparator.charAt(10)) &&
  1941. Character.isLetterOrDigit(emaidWithoutSeparator.charAt(11)) &&
  1942. Character.isLetterOrDigit(emaidWithoutSeparator.charAt(12)) &&
  1943. Character.isLetterOrDigit(emaidWithoutSeparator.charAt(13))) ) {
  1944. getLogger().error("EMAID (" + emaid + ") is invalid, the eMA instance must be alpha-numerical");
  1945. return false;
  1946. }
  1947. return true;
  1948. }
  1949. public static void setExiCodec(ExiCodec exiCodecChoice) {
  1950. exiCodec = exiCodecChoice;
  1951. }
  1952. private static ExiCodec getExiCodec() {
  1953. return exiCodec;
  1954. }
  1955. }