| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274 |
- /*******************************************************************************
- * The MIT License (MIT)
- *
- * Copyright (c) 2015-207 V2G Clarity (Dr.-Ing. Marc Mültin)
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *******************************************************************************/
- package com.v2gclarity.risev2g.shared.utils;
- import java.io.ByteArrayInputStream;
- import java.io.ByteArrayOutputStream;
- import java.io.FileInputStream;
- import java.io.FileNotFoundException;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.InputStream;
- import java.math.BigInteger;
- import java.nio.file.Files;
- import java.nio.file.Path;
- import java.nio.file.Paths;
- import java.security.AlgorithmParameters;
- import java.security.InvalidAlgorithmParameterException;
- import java.security.InvalidKeyException;
- import java.security.Key;
- import java.security.KeyFactory;
- import java.security.KeyManagementException;
- import java.security.KeyPair;
- import java.security.KeyPairGenerator;
- import java.security.KeyStore;
- import java.security.KeyStoreException;
- import java.security.MessageDigest;
- import java.security.NoSuchAlgorithmException;
- import java.security.NoSuchProviderException;
- import java.security.PublicKey;
- import java.security.SecureRandom;
- import java.security.Signature;
- import java.security.SignatureException;
- import java.security.UnrecoverableKeyException;
- import java.security.cert.Certificate;
- import java.security.cert.CertificateEncodingException;
- import java.security.cert.CertificateException;
- import java.security.cert.CertificateExpiredException;
- import java.security.cert.CertificateFactory;
- import java.security.cert.CertificateNotYetValidException;
- import java.security.cert.X509Certificate;
- import java.security.interfaces.ECPrivateKey;
- import java.security.interfaces.ECPublicKey;
- import java.security.spec.ECGenParameterSpec;
- import java.security.spec.ECParameterSpec;
- import java.security.spec.ECPoint;
- import java.security.spec.ECPrivateKeySpec;
- import java.security.spec.ECPublicKeySpec;
- import java.security.spec.InvalidKeySpecException;
- import java.security.spec.InvalidParameterSpecException;
- import java.security.spec.PKCS8EncodedKeySpec;
- import java.util.ArrayList;
- import java.util.Calendar;
- import java.util.Date;
- import java.util.Enumeration;
- import java.util.HashMap;
- import java.util.List;
- import java.util.concurrent.TimeUnit;
- import javax.crypto.BadPaddingException;
- import javax.crypto.Cipher;
- import javax.crypto.EncryptedPrivateKeyInfo;
- import javax.crypto.IllegalBlockSizeException;
- import javax.crypto.KeyAgreement;
- import javax.crypto.NoSuchPaddingException;
- import javax.crypto.SecretKey;
- import javax.crypto.SecretKeyFactory;
- import javax.crypto.spec.IvParameterSpec;
- import javax.crypto.spec.PBEKeySpec;
- import javax.crypto.spec.SecretKeySpec;
- import javax.naming.InvalidNameException;
- import javax.naming.ldap.LdapName;
- import javax.naming.ldap.Rdn;
- import javax.net.ssl.KeyManager;
- import javax.net.ssl.KeyManagerFactory;
- import javax.net.ssl.SSLContext;
- import javax.net.ssl.TrustManager;
- import javax.net.ssl.TrustManagerFactory;
- import javax.security.auth.x500.X500Principal;
- import javax.xml.bind.JAXBElement;
- import org.apache.logging.log4j.LogManager;
- import org.apache.logging.log4j.Logger;
- import com.v2gclarity.risev2g.shared.enumerations.GlobalValues;
- import com.v2gclarity.risev2g.shared.enumerations.PKI;
- import com.v2gclarity.risev2g.shared.exiCodec.ExiCodec;
- import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CanonicalizationMethodType;
- import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.CertificateChainType;
- import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ContractSignatureEncryptedPrivateKeyType;
- import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.DiffieHellmanPublickeyType;
- import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.DigestMethodType;
- import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.EMAIDType;
- import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ListOfRootCertificateIDsType;
- import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ReferenceType;
- import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
- import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SignatureMethodType;
- import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SignatureType;
- import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SignedInfoType;
- import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.SubCertificatesType;
- import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.TransformType;
- import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.TransformsType;
- import com.v2gclarity.risev2g.shared.v2gMessages.msgDef.X509IssuerSerialType;
- import java.util.Base64;
- public final class SecurityUtils {
- /*
- * Add VM (virtual machine) argument "-Djavax.net.debug=ssl" if you want more detailed debugging output
- */
-
- static Logger logger = LogManager.getLogger(SecurityUtils.class.getSimpleName());
- static ExiCodec exiCodec;
- static boolean showSignatureVerificationLog = ((boolean) MiscUtils.getPropertyValue("signature.verification.showlog"));
-
- public static enum ContractCertificateStatus {
- UPDATE_NEEDED,
- INSTALLATION_NEEDED,
- OK,
- UNKNOWN // is used as default for communication session context
- }
-
- public static Logger getLogger() {
- return logger;
- }
-
-
- /**
- * Returns the standard JKS keystore which holds the respective credentials (private key and
- * certificate chain) for the EVCC or SECC (whoever calls this method).
- *
- * The keystore file itself must reside outside the JAR file, at the same level as the JAR file itself,
- * because
- * a) at least the evccKeystore needs to be editable when installing the contract certificate (JAR file is read-only), and
- * b) it is very likely that private keys and certificate chains might be stored separately in a secure hardware module.
- * Therefore, the file is not loaded with getResourceAsStream(), but with a FileInputStream.
- *
- * @param keyStorePath The relative path and file name of the keystore
- * @param keyStorePassword The password which protects the keystore
- * @return The respective keystore
- */
- public static KeyStore getKeyStore(String keyStorePath, String keyStorePassword) {
- FileInputStream keyStore;
-
- try {
- keyStore = new FileInputStream(keyStorePath);
- return getKeyStore(keyStore, keyStorePassword, "jks");
- } catch (FileNotFoundException e) {
- getLogger().error("Keystore file location '" + keyStorePath + "' not found (FileNotFoundException).");
- return null;
- }
- }
-
- /**
- * Returns the standard JKS truststore which holds the respective trusted certificates for the EVCC
- * or SECC (whoever calls this method).
- *
- * The truststore file itself must reside outside the JAR file, at the same level as the JAR file itself,
- * because
- * a) at least the evccKeystore needs to be editable when installing the contract certificate (JAR file is read-only), and
- * b) it is very likely that private keys and certificate chains might be stored separately in a secure hardware module.
- * Therefore, the file is not loaded with getResourceAsStream(), but with a FileInputStream.
- *
- * @param trustStorePath The relative path and file name of the truststore
- * @param trustStorePassword The password which protects the truststore
- * @return The respective truststore
- */
- public static KeyStore getTrustStore(String trustStorePath, String trustStorePassword) {
- FileInputStream trustStore;
-
- try {
- trustStore = new FileInputStream(trustStorePath);
- return getKeyStore(trustStore, trustStorePassword, "jks");
- } catch (FileNotFoundException e) {
- getLogger().error("Truststore file location '" + trustStorePath + "' not found (FileNotFoundException).");
- return null;
- }
- }
-
-
- /**
- * Returns a PKCS#12 container which holds the respective credentials (private key and certificate chain)
- *
- * @param pkcs12Path The relative path and file name of the PKCS#12 container
- * @param password The password which protects the PKCS#12 container
- * @return The respective keystore
- */
- public static KeyStore getPKCS12KeyStore(String pkcs12Path, String password) {
- FileInputStream fis = null;
-
- try {
- fis = new FileInputStream(pkcs12Path);
- return getKeyStore(fis, password, "pkcs12");
- } catch (FileNotFoundException e) {
- getLogger().error("FileNotFoundException occurred while trying to access PKCS#12 container at " +
- "location '" + pkcs12Path + "'");
- return null;
- }
- }
- /**
- * Returns a standard keystore which holds the respective credentials (private key and certificate chain).
- *
- * @param keyStoreIS The input stream of the keystore
- * @param keyStorePassword The password which protects the keystore
- * @param keyStoreType The type of the keystore, either "jks" or "pkcs12"
- * @return The respective keystore
- */
- private static KeyStore getKeyStore(InputStream keyStoreIS, String keyStorePassword, String keyStoreType) {
- KeyStore keyStore = null;
-
- try {
- keyStore = KeyStore.getInstance(keyStoreType);
- keyStore.load(keyStoreIS, keyStorePassword.toCharArray());
- keyStoreIS.close();
- return keyStore;
- } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException |
- IOException | NullPointerException e) {
- getLogger().error(e.getClass().getSimpleName() + " occurred while trying to load keystore", e);
- }
-
- return null;
- }
-
-
- /**
- * Checks whether the given certificate is currently valid with regards to date and time.
- *
- * @param certificate The X509Certificiate to be checked for validity
- * @return ResponseCode FAILED_CertificateExpired, if the certificate is expired. FAILED, if the certificate is
- * not yet valid, since there is no other proper response code available. OK, otherwise.
- */
- public static ResponseCodeType verifyValidityPeriod(X509Certificate certificate) {
- try {
- certificate.checkValidity();
- return ResponseCodeType.OK;
- } catch (CertificateExpiredException e) {
- X500Principal subject = certificate.getSubjectX500Principal();
-
- getLogger().warn("Certificate with distinguished name '" + subject.getName() +
- "' already expired (not after " + certificate.getNotAfter() + ")");
- return ResponseCodeType.FAILED_CERTIFICATE_EXPIRED;
- } catch (CertificateNotYetValidException e) {
- X500Principal subject = certificate.getSubjectX500Principal();
- getLogger().warn("Certificate with distinguished name '" + subject.getName() +
- "' not yet valid (not before " + certificate.getNotBefore() + ")");
- return ResponseCodeType.FAILED;
- }
- }
-
-
- /**
- * Domain Component restrictions: <br/>
- * - SECC certificate: "CPO" (verification by EVCC) <br/>
- * - CPS leaf certificate: "CPS" (verification by EVCC) <br/>
- * - OEM Provisioning Certificate: "OEM" (verification by provisioning service (neither EVCC nor SECC))
- *
- * @param certificate The X509Certificiate to be checked for validity
- * @param domainComponent The domain component to be checked for in the distinguished name of the certificate
- * @return True, if the given domain component is present in the distinguished name, false otherwise
- */
- public static boolean verifyDomainComponent(X509Certificate certificate, String domainComponent) {
- String dn = certificate.getSubjectX500Principal().getName();
- LdapName ln;
-
- try {
- ln = new LdapName(dn);
-
- for (Rdn rdn : ln.getRdns()) {
- if (rdn.getType().equalsIgnoreCase("DC") && rdn.getValue().equals(domainComponent)) {
- return true;
- }
- }
- } catch (InvalidNameException e) {
- getLogger().error("InvalidNameException occurred while trying to check domain component of certificate", e);
- }
-
- getLogger().error("Expected domain component (DC) '" + domainComponent + "' not found in certificate "
- + "with distinguished name '" + dn + "'");
- return false;
- }
-
-
- /**
- * Checks how many days a given certificate is still valid.
- * If the certificate is not valid any more, a negative number will be returned according to the number
- * of days the certificate is already expired.
- *
- * @param certificate The X509Certificiate to be checked for validity period
- * @return The number of days the given certificate is still valid, a negative number if already expired.
- */
- public static short getValidityPeriod(X509Certificate certificate) {
- Date today = Calendar.getInstance().getTime();
- Date certificateExpirationDate = certificate.getNotAfter();
- long diff = certificateExpirationDate.getTime() - today.getTime();
-
- return (short) TimeUnit.DAYS.convert(diff, TimeUnit.MILLISECONDS);
- }
-
- /**
- * Executes the following validity checks:
- * <br/><br/>
- * 1. Verifies the signature for each certificate in the given certificate chain all the way up to the trust
- * anchor. Certificates in certificate chain must be in the right order (leaf -> Sub-CA2 -> Sub-CA1) <br/>
- * 2. Verifies whether the given certificate is currently valid with regards to date and time.<br/>
- * 3. Verifies that certificate attributes are set correctly, depending on the PKI the certificate chain belongs to
- *
- * @param certChain The certificate chain to iterate over to check for validity
- * @param trustStoreFileName The relative path and file name of the truststore
- * @param pki The Public Key Infrastructure to which the certChain belongs (a PKI enumeration value)
- * @return ResponseCode applicable to the verification steps
- */
- public static ResponseCodeType verifyCertificateChain(
- CertificateChainType certChain,
- String trustStoreFileName,
- PKI pki) {
- X509Certificate leafCertificate = null;
- X509Certificate subCA1Certificate = null;
- X509Certificate subCA2Certificate = null;
- ResponseCodeType responseCode = null;
-
- // Get leaf certificate
- if (certChain != null) {
- leafCertificate = getCertificate(certChain.getCertificate());
- } else {
- getLogger().error("Signature verification failed because provided certificate chain is empty (null)");
- return ResponseCodeType.FAILED_CERT_CHAIN_ERROR;
- }
-
- // Get Sub-CA certificates
- if (leafCertificate != null) {
- SubCertificatesType subCertificates = certChain.getSubCertificates();
-
- if (subCertificates != null && subCertificates.getCertificate().size() != 0) {
- subCA2Certificate = getCertificate(subCertificates.getCertificate().get(0));
-
- if (subCertificates.getCertificate().size() == 2)
- subCA1Certificate = getCertificate(subCertificates.getCertificate().get(1));
- } else {
- getLogger().error("Signature verification failed because no Sub-CA certificates available in provided "
- + "certificate chain");
- return ResponseCodeType.FAILED_CERT_CHAIN_ERROR;
- }
- } else {
- getLogger().error("Signature verification failed because no leaf certificate available in provided "
- + "certificate chain");
- return ResponseCodeType.FAILED_CERT_CHAIN_ERROR;
- }
-
-
- /*
- * ****************
- * SIGNATURE CHECKS
- * ****************
- */
-
- // Check signature of leaf certificate
- if (!verifySignature(leafCertificate, subCA2Certificate)) return ResponseCodeType.FAILED_CERT_CHAIN_ERROR;
-
- // Check signature of Sub-CA 2 and optionally, if present, Sub-CA 2 certificate
- if (subCA1Certificate != null) {
- if (!verifySignature(subCA2Certificate, subCA1Certificate)) return ResponseCodeType.FAILED_CERT_CHAIN_ERROR;
- if (!verifySignature(subCA1Certificate, trustStoreFileName)) return ResponseCodeType.FAILED_CERT_CHAIN_ERROR;
- } else {
- // In case there is only one intermediate certificate (profile of Sub-CA 2)
- if (!verifySignature(subCA2Certificate, trustStoreFileName)) return ResponseCodeType.FAILED_CERT_CHAIN_ERROR;
- }
-
-
- /*
- * **********************
- * VALIDITY PERIOD CHECKS
- * **********************
- */
- ResponseCodeType validityResponseCode = null;
-
- // Check validity of leaf certificate
- validityResponseCode = verifyValidityPeriod(leafCertificate);
- if (!validityResponseCode.equals(ResponseCodeType.OK)) return validityResponseCode;
-
- // Check validity of Sub-CA2 certificate
- validityResponseCode = verifyValidityPeriod(subCA2Certificate);
- if (!validityResponseCode.equals(ResponseCodeType.OK)) return validityResponseCode;
-
- // Check validity of Sub-CA1 certificate, if present
- if (subCA1Certificate != null) {
- validityResponseCode = verifyValidityPeriod(subCA1Certificate);
- if (!validityResponseCode.equals(ResponseCodeType.OK)) return validityResponseCode;
- }
-
-
- /*
- * ***********************************
- * COMMON CERTIFICATE ATTRIBUTES CHECK
- * ***********************************
- */
-
- // Check pathLenContraint (maximum number of non-self-issued intermediate certificates that may follow this certificate)
- if (subCA2Certificate.getBasicConstraints() != 0) {
- getLogger().error("Sub-CA 2 certificate with distinguished name '" +
- subCA2Certificate.getSubjectX500Principal().getName() + "' has incorrect value for " +
- "pathLenConstraint. Should be 0 instead of " + subCA2Certificate.getBasicConstraints());
- return ResponseCodeType.FAILED_CERTIFICATE_EXPIRED;
- }
-
- if (subCA1Certificate != null && subCA1Certificate.getBasicConstraints() != 1) {
- getLogger().error("Sub-CA 1 certificate with distinguished name '" +
- subCA1Certificate.getSubjectX500Principal().getName() + "' has incorrect value for " +
- "pathLenConstraint. Should be 1 instead of " + subCA2Certificate.getBasicConstraints());
- return ResponseCodeType.FAILED_CERTIFICATE_EXPIRED;
- }
-
-
- responseCode = verifyLeafCertificateAttributes(leafCertificate, pki);
- if (responseCode.equals(ResponseCodeType.OK))
- return responseCode;
-
- return ResponseCodeType.OK;
- }
-
-
- /**
- * Checks certificate attributes for a given leaf certificate belonging to an ISO 15118 PKI.
- *
- * @param certificate The X.509 certificate whose attributes need to be checked
- * @param pki The PKI to which the certificate belongs
- * @return
- */
- public static ResponseCodeType verifyLeafCertificateAttributes(X509Certificate leafCertificate, PKI pki) {
- switch (pki) {
- case CPO:
- if (!verifyDomainComponent(leafCertificate, "CPO")) {
- getLogger().error("SECC leaf certificate with distinguished name '" +
- leafCertificate.getSubjectX500Principal().getName() + "' has incorrect value for " +
- "domain component. Should be 'CPO'");
- return ResponseCodeType.FAILED_CERT_CHAIN_ERROR;
- }
- break;
- case CPS:
- if (!verifyDomainComponent(leafCertificate, "CPS")) {
- getLogger().error("CPS leaf certificate with distinguished name '" +
- leafCertificate.getSubjectX500Principal().getName() + "' has incorrect value for " +
- "domain component. Should be 'CPS'");
- return ResponseCodeType.FAILED_CERT_CHAIN_ERROR;
- }
- break;
- case MO:
- if (!isEMAIDSyntaxValid(leafCertificate)) {
- return ResponseCodeType.FAILED_CERT_CHAIN_ERROR;
- }
- break;
- case OEM:
- if (!verifyDomainComponent(leafCertificate, "OEM")) {
- getLogger().error("OEM provisioning certificate with distinguished name '" +
- leafCertificate.getSubjectX500Principal().getName() + "' has incorrect value for " +
- "domain component. Should be 'OEM'");
- return ResponseCodeType.FAILED_CERT_CHAIN_ERROR;
- }
- break;
- default:
- break;
- }
-
- return ResponseCodeType.OK;
- }
-
-
- /**
- * Verifies that the given certificate was signed using the private key that corresponds to the
- * public key of the provided certificate.
- *
- * @param certificate The X509Certificate which is to be checked
- * @param issuingCertificate The X.509 certificate which holds the public key corresponding to the private
- * key with which the given certificate should have been signed
- * @return True, if the verification was successful, false otherwise
- */
- public static boolean verifySignature(X509Certificate certificate, X509Certificate issuingCertificate) {
- X500Principal subject = certificate.getSubjectX500Principal();
- X500Principal expectedIssuerSubject = certificate.getIssuerX500Principal();
- X500Principal issuerSubject = issuingCertificate.getSubjectX500Principal();
- PublicKey publicKeyForSignature = issuingCertificate.getPublicKey();
-
- try {
- certificate.verify(publicKeyForSignature);
- return true;
- } catch (InvalidKeyException | CertificateException | NoSuchAlgorithmException |
- NoSuchProviderException | SignatureException e) {
- getLogger().warn("\n"
- + "\tSignature verification of certificate having distinguished name \n"
- + "\t'" + subject.getName() + "'\n"
- + "\twith certificate having distinguished name (the issuer) \n"
- + "\t'" + issuerSubject.getName() + "'\n"
- + "\tfailed. Expected issuer has distinguished name \n"
- + "\t'" + expectedIssuerSubject.getName() + "' (" + e.getClass().getSimpleName() + ")", e);
- }
-
- return false;
- }
-
-
- /**
- * Iterates over the certificates stored in the truststore to verify the signature of the provided certificate
- *
- * @param trustStoreFilename The relative path and file name of the truststore
- * @param certificate The certificate whose signature needs to be signed
- * @return True, if the provided certificate has been signed by one of the certificates in the
- * truststore, false otherwise
- */
- public static boolean verifySignature(X509Certificate certificate, String trustStoreFilename) {
- KeyStore trustStore = SecurityUtils.getTrustStore(trustStoreFilename, GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString());
- X500Principal expectedIssuer = certificate.getIssuerX500Principal();
-
- try {
- Enumeration<String> aliases = trustStore.aliases();
- while (aliases.hasMoreElements()) {
- X509Certificate rootCA = (X509Certificate) trustStore.getCertificate(aliases.nextElement());
- if (rootCA.getSubjectX500Principal().getName().equals(expectedIssuer.getName()) &&
- verifySignature(certificate, rootCA)) return true;
- }
- } catch (KeyStoreException | NullPointerException e) {
- getLogger().error(e.getClass().getSimpleName() + " occurred while trying to verify trust " +
- "status of certificate with distinguished name '" +
- certificate.getSubjectX500Principal().getName() + "' with truststore at " +
- "location '" + trustStoreFilename + "'", e);
- }
-
- return false;
- }
-
-
- /**
- * Returns the leaf certificate from a given certificate chain.
- *
- * @param certChain The certificate chain given as an array of Certificate instances
- * @return The leaf certificate (begin not a CA)
- */
- public static X509Certificate getLeafCertificate(Certificate[] certChain) {
- for (Certificate cert : certChain) {
- X509Certificate x509Cert = (X509Certificate) cert;
- // Check whether the pathLen constraint is set which indicates if this certificate is a CA
- if (x509Cert.getBasicConstraints() == -1) return x509Cert;
- }
-
- getLogger().warn("No leaf certificate found in given certificate chain");
- return null;
- }
-
-
- /**
- * Returns the intermediate certificates (sub CAs) from a given certificate chain.
- *
- * @param certChain The certificate chain given as an array of Certificate instances
- * @return The sub certificates given as a list of byte arrays contained in a SubCertiticatesType instance
- */
- public static SubCertificatesType getSubCertificates(Certificate[] certChain) {
- SubCertificatesType subCertificates = new SubCertificatesType();
-
- for (Certificate cert : certChain) {
- X509Certificate x509Cert = (X509Certificate) cert;
- // Check whether the pathLen constraint is set which indicates if this certificate is a CA
- if (x509Cert.getBasicConstraints() != -1)
- try {
- subCertificates.getCertificate().add(x509Cert.getEncoded());
- } catch (CertificateEncodingException e) {
- X500Principal subject = x509Cert.getIssuerX500Principal();
- getLogger().error("A CertificateEncodingException occurred while trying to get certificate " +
- "with distinguished name '" + subject.getName().toString() + "'", e);
- }
- }
-
- if (subCertificates.getCertificate().size() == 0) {
- getLogger().warn("No intermediate CAs found in given certificate array");
- }
-
- return subCertificates;
- }
-
-
- /**
- * Returns the list of X509IssuerSerialType instances of the root CAs contained in the truststore.
- *
- * @param trustStoreFileName The relative path and file name of the truststore
- * @param trustStorePassword The password which protects the truststore
- * @return The list of X509IssuerSerialType instances of the root CAs
- */
- public static ListOfRootCertificateIDsType getListOfRootCertificateIDs(
- String trustStoreFileName,
- String trustStorePassword) {
- KeyStore evccTrustStore = getTrustStore(trustStoreFileName, trustStorePassword);
- ListOfRootCertificateIDsType rootCertificateIDs = new ListOfRootCertificateIDsType();
-
- X509Certificate cert = null;
- try {
- Enumeration<String> aliases = evccTrustStore.aliases();
- while (aliases.hasMoreElements()) {
- cert = (X509Certificate) evccTrustStore.getCertificate(aliases.nextElement());
- X509IssuerSerialType serialType = new X509IssuerSerialType();
- serialType.setX509IssuerName(cert.getIssuerX500Principal().getName());
- serialType.setX509SerialNumber(cert.getSerialNumber());
- rootCertificateIDs.getRootCertificateID().add(serialType);
- }
- } catch (KeyStoreException | NullPointerException e) {
- getLogger().error(e.getClass().getSimpleName() + " occurred while trying to get list of " +
- "root certificate IDs from truststore at location '" + trustStoreFileName + "'", e);
- }
-
- return rootCertificateIDs;
- }
-
-
- /**
- * Returns an instance of a X.509 certificate created from its raw byte array
- *
- * @param certificate The byte array representing a X.509 certificate
- * @return The X.509 certificate
- */
- public static X509Certificate getCertificate(byte[] certificate) {
- X509Certificate cert = null;
-
- try {
- InputStream in = new ByteArrayInputStream(certificate);
- CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
- cert = (X509Certificate) certFactory.generateCertificate(in);
- } catch (CertificateException e) {
- getLogger().error("CertificateException occurred when trying to create X.509 certificate from byte array", e);
- }
-
- return cert;
- }
-
-
- /**
- * Returns the mobility operator Sub-CA 2 certificate (MOSubCA2 certificate) which can verify the signature of the
- * contract certificate from the given keystore. The public key of the MOSub2Certificate is then used to verify
- * the signature of sales tariffs.
- *
- * @param keyStoreFileName The relative path and file name of the keystore
- * @return The X.509 mobility operator Sub-CA2 certificate (a certificate from a Sub-CA)
- */
- public static X509Certificate getMOSubCA2Certificate(String keyStoreFileName) {
- KeyStore keystore = getKeyStore(keyStoreFileName, GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString());
- X509Certificate moSubCA2Certificate = null;
-
- try {
- Certificate[] certChain = keystore.getCertificateChain(GlobalValues.ALIAS_CONTRACT_CERTIFICATE.toString());
- X509Certificate contractCertificate = getLeafCertificate(certChain);
- SubCertificatesType subCertificates = getSubCertificates(certChain);
-
- for (byte[] certificate : subCertificates.getCertificate()) {
- X509Certificate x509Cert = getCertificate(certificate);
- if (contractCertificate.getIssuerX500Principal().getName().equals(
- x509Cert.getSubjectX500Principal().getName())) {
- moSubCA2Certificate = x509Cert;
- break;
- }
- }
- } catch (KeyStoreException e) {
- getLogger().error("KeyStoreException occurred while trying to get MOSubCA2 certificate");
- }
-
- return moSubCA2Certificate;
- }
-
- /**
- * Returns the ECPublicKey instance from its encoded raw bytes.
- * The first byte has the fixed value 0x04 indicating the uncompressed form.
- * Therefore, the byte array must be of form: [0x04, x coord of point (32 bytes), y coord of point (32 bytes)]
- *
- * @param publicKeyBytes The byte array representing the encoded raw bytes of the public key
- * @return The ECPublicKey instance
- */
- public static ECPublicKey getPublicKey(byte[] publicKeyBytes) {
- // First we separate x and y of coordinates into separate variables
- byte[] x = new byte[32];
- byte[] y = new byte[32];
- System.arraycopy(publicKeyBytes, 1, x, 0, 32);
- System.arraycopy(publicKeyBytes, 33, y, 0, 32);
-
- try {
- KeyFactory kf = KeyFactory.getInstance("EC");
-
- AlgorithmParameters parameters = AlgorithmParameters.getInstance("EC");
- parameters.init(new ECGenParameterSpec("secp256r1"));
- ECParameterSpec ecParameterSpec = parameters.getParameterSpec(ECParameterSpec.class);
-
- ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(x), new BigInteger(y)), ecParameterSpec);
- ECPublicKey ecPublicKey = (ECPublicKey) kf.generatePublic(ecPublicKeySpec);
- return ecPublicKey;
- } catch (NoSuchAlgorithmException | InvalidParameterSpecException | InvalidKeySpecException e) {
- getLogger().error(e.getClass().getSimpleName() + " occurred when trying to get public key from raw bytes", e);
- return null;
- }
- }
-
-
- /**
- * Returns the public key part of an elliptic curve Diffie-Hellman keypair
- *
- * @param ecdhKeyPair The elliptic curve Diffie-Hellman keypair
- * @return The respective public key
- */
- public static DiffieHellmanPublickeyType getDHPublicKey(KeyPair ecdhKeyPair) {
- DiffieHellmanPublickeyType dhPublicKey = new DiffieHellmanPublickeyType();
- /*
- * Experience from the test symposium in San Diego (April 2016):
- * The Id element of the signature is not restricted in size by the standard itself. But on embedded
- * systems, the memory is very limited which is why we should not use long IDs for the signature reference
- * element. A good size would be 3 characters max (like the example in the ISO 15118-2 annex J)
- */
- dhPublicKey.setId("id1");
-
- byte[] uncompressedDHpublicKey = getUncompressedSubjectPublicKey((ECPublicKey) ecdhKeyPair.getPublic());
-
- getLogger().debug("Created DHpublickey: " + ByteUtils.toHexString(uncompressedDHpublicKey));
- dhPublicKey.setValue(uncompressedDHpublicKey);
-
- return dhPublicKey;
- }
-
-
- /**
- * Returns the ECPrivateKey instance from its raw bytes. Note that you must provide the "s" value of the
- * private key, not e.g. the byte array from reading a PKCS#8 key file.
- *
- * @param privateKeyBytes The byte array (the "s" value) of the private key
- * @return The ECPrivateKey instance
- */
- public static ECPrivateKey getPrivateKey(byte[] privateKeyBytes) {
- try {
- AlgorithmParameters parameters = AlgorithmParameters.getInstance("EC");
- parameters.init(new ECGenParameterSpec("secp256r1"));
-
- ECParameterSpec ecParameterSpec = parameters.getParameterSpec(ECParameterSpec.class);
- ECPrivateKeySpec ecPrivateKeySpec = new ECPrivateKeySpec(new BigInteger(privateKeyBytes), ecParameterSpec);
-
- ECPrivateKey privateKey = (ECPrivateKey) KeyFactory.getInstance("EC").generatePrivate(ecPrivateKeySpec);
- return privateKey;
- } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidParameterSpecException e) {
- getLogger().error(e.getClass().getSimpleName() + " occurred when trying to get private key from raw bytes", e);
- return null;
- }
- }
-
-
- /**
- * Searches the given keystore for the private key. It is assumed that the given keystore holds
- * only one private key entry whose alias is not known before, which is the case during certificate
- * installation when the SECC uses a PKCS#12 container encapsulating the
- * contract certificate, its private key and an optional chain of intermediate CAs.
- *
- * @param keyStore The PKCS#12 keystore
- * @return The private key contained in the given keystore as an ECPrivateKey
- */
- public static ECPrivateKey getPrivateKey(KeyStore keyStore) {
- ECPrivateKey privateKey = null;
-
- try {
- Enumeration<String> aliases = keyStore.aliases();
- // Only one certificate chain (and therefore alias) should be available
- while (aliases.hasMoreElements()) {
- privateKey = (ECPrivateKey) keyStore.getKey(
- aliases.nextElement(),
- GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString().toCharArray());
- }
- } catch (KeyStoreException | UnrecoverableKeyException | NoSuchAlgorithmException |
- NullPointerException e) {
- getLogger().error(e.getClass().getSimpleName() + " occurred while trying to get private " +
- "key from keystore", e);
- }
-
- return privateKey;
- }
-
-
- /**
- * Reads the private key from an encrypted PKCS#8 file and returns it as an ECPrivateKey instance.
- *
- * @param A PKCS#8 (.key) file containing the private key with value "s"
- * @return The private key as an ECPrivateKey instance
- */
- public static ECPrivateKey getPrivateKey(String keyFilePath) {
- Path fileLocation = Paths.get(keyFilePath);
- byte[] pkcs8ByteArray;
-
- try {
- pkcs8ByteArray = Files.readAllBytes(fileLocation);
-
- // The DER encoded private key is password-based encrypted and provided in PKCS#8. So we need to decrypt it first
- PBEKeySpec pbeKeySpec = new PBEKeySpec(GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString().toCharArray());
- EncryptedPrivateKeyInfo encryptedPrivKeyInfo = new EncryptedPrivateKeyInfo(pkcs8ByteArray);
- SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(encryptedPrivKeyInfo.getAlgName());
- Key secret = secretKeyFactory.generateSecret(pbeKeySpec);
- PKCS8EncodedKeySpec pkcs8PrivKeySpec = encryptedPrivKeyInfo.getKeySpec(secret);
-
- ECPrivateKey privateKey = (ECPrivateKey) KeyFactory.getInstance("EC").generatePrivate(pkcs8PrivKeySpec);
- return privateKey;
- } catch (IOException | InvalidKeySpecException | NoSuchAlgorithmException | InvalidKeyException e) {
- getLogger().error(e.getClass().getSimpleName() + " occurred while trying to access private key at " +
- "location '" + keyFilePath + "'");
- return null;
- }
- }
-
-
- /**
- * Searches the given keystore for the private key which corresponds to the provided alias.
- * Example: In case of the EVCC and during certificate installation, the private key of the
- * OEM provisioning certificate is needed. During certificate update, the private key of the
- * existing contract certificate is needed.
- *
- * @param keyStore The keystore of EVCC or SECC
- * @param alias The alias of a specific private key entry
- * @return The private key corresponding to the respective alias in the given keystore
- */
- public static ECPrivateKey getPrivateKey(KeyStore keyStore, String alias) {
- ECPrivateKey privateKey = null;
-
- try {
- privateKey = (ECPrivateKey) keyStore.getKey(
- alias,
- GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString().toCharArray());
- } catch (KeyStoreException | UnrecoverableKeyException | NoSuchAlgorithmException e) {
- getLogger().error("The private key from keystore with alias '" + alias +
- "' could not be retrieved (" + e.getClass().getSimpleName() + ")", e);
- }
-
- return privateKey;
- }
-
- /**
- * Returns the SecretKey instance from its raw bytes
- *
- * @param key The byte array representing the symmetric SecretKey instance
- * @return The SecretKey instance
- */
- public static SecretKey getSecretKey(byte[] key) {
- SecretKey secretKey = new SecretKeySpec(key, 0, key.length, "DiffieHellman");
-
- return secretKey;
- }
-
-
- /**
- * Returns the certificate chain from a PKCS#12 container holding credentials such as private key,
- * leaf certificate and zero or more intermediate certificates.
- *
- * @param pkcs12Resource The PKCS#12 container
- * @return The certificate chain
- */
- public static CertificateChainType getCertificateChain(String pkcs12Resource) {
- CertificateChainType certChain = new CertificateChainType();
-
- /*
- * For testing purposes, the respective PKCS12 container file has already been put in the
- * resources folder. However, when implementing a real interface to a secondary actor's backend,
- * the retrieval of a certificate must be done via some other online mechanism.
- */
- KeyStore contractCertificateKeystore = getPKCS12KeyStore(pkcs12Resource, GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString());
-
- if (contractCertificateKeystore == null) {
- getLogger().error("Unable to access certificate chain because no PKCS#12 container found at " +
- "location '" + pkcs12Resource + "'");
- return null;
- }
-
- try {
- Enumeration<String> aliases = contractCertificateKeystore.aliases();
- Certificate[] tempCertChain = null;
- // Only one certificate chain (and therefore alias) should be available
- while (aliases.hasMoreElements()) {
- tempCertChain = contractCertificateKeystore.getCertificateChain(aliases.nextElement());
- certChain.setCertificate(getLeafCertificate(tempCertChain).getEncoded());
- certChain.setSubCertificates(getSubCertificates(tempCertChain));
- }
- } catch (KeyStoreException | CertificateEncodingException | NullPointerException e) {
- getLogger().error(e.getClass().getSimpleName() + " occurred while trying to get " +
- "certificate chain from resource '" + pkcs12Resource + "'", e);
- }
-
- return certChain;
- }
-
-
- /**
- * Returns the SignedInfo element of the V2GMessage header, based on the provided HashMap which holds
- * the reference IDs (URIs) and the corresponding SHA-256 digests.
- *
- * @param xmlSignatureRefElements A HashMap of Strings (reflecting the reference IDs) and digest values
- * @return The SignedInfoType instance
- */
- public static SignedInfoType getSignedInfo(HashMap<String, byte[]> xmlSignatureRefElements) {
- /*
- * According to requirement [V2G2-771] in ISO/IEC 15118-2 the following message elements of the
- * XML signature framework shall not be used:
- * - Id (attribute in SignedInfo)
- * - ##any in SignedInfo – CanonicalizationMethod
- * - HMACOutputLength in SignedInfo – SignatureMethod
- * - ##other in SignedInfo – SignatureMethod
- * - Type (attribute in SignedInfo-Reference)
- * - ##other in SignedInfo – Reference – Transforms – Transform
- * - XPath in SignedInfo – Reference – Transforms – Transform
- * - ##other in SignedInfo – Reference – DigestMethod
- * - Id (attribute in SignatureValue)
- * - Object (in Signature)
- * - KeyInfo
- */
- DigestMethodType digestMethod = new DigestMethodType();
- digestMethod.setAlgorithm("http://www.w3.org/2001/04/xmlenc#sha256");
-
- TransformType transform = new TransformType();
- transform.setAlgorithm("http://www.w3.org/TR/canonical-exi/");
- TransformsType transforms = new TransformsType();
- transforms.getTransform().add(transform);
-
- List<ReferenceType> references = new ArrayList<ReferenceType>();
- xmlSignatureRefElements.forEach( (k,v) -> {
- ReferenceType reference = new ReferenceType();
- reference.setDigestMethod(digestMethod);
- reference.setDigestValue(v);
- reference.setTransforms(transforms);
- reference.setURI("#" + k);
-
- references.add(reference);
- });
-
- CanonicalizationMethodType canonicalizationMethod = new CanonicalizationMethodType();
- canonicalizationMethod.setAlgorithm("http://www.w3.org/TR/canonical-exi/");
-
- SignatureMethodType signatureMethod = new SignatureMethodType();
- signatureMethod.setAlgorithm("http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256");
-
- SignedInfoType signedInfo = new SignedInfoType();
- signedInfo.setCanonicalizationMethod(canonicalizationMethod);
- signedInfo.setSignatureMethod(signatureMethod);
- signedInfo.getReference().addAll(references);
-
- return signedInfo;
- }
-
-
- /**
- * Saves the newly received contract certificate chain, provided by CertificateInstallationRes or
- * CertificateUpdateRes.
- *
- * @param keyStorePassword The password which protects the EVCC keystore
- * @param contractCertChain The certificate chain belonging to the contract certificate
- * @param contractCertPrivateKey The private key corresponding to the public key of the leaf certificate
- * stored in the certificate chain
- * @return True, if the contract certificate chain and private key could be saved, false otherwise
- */
- public static boolean saveContractCertificateChain(
- String keyStorePassword,
- CertificateChainType contractCertChain,
- ECPrivateKey contractCertPrivateKey) {
- KeyStore keyStore = getKeyStore(GlobalValues.EVCC_KEYSTORE_FILEPATH.toString(), keyStorePassword);
- try {
- if (isPrivateKeyValid(contractCertPrivateKey, contractCertChain)) {
- keyStore.setKeyEntry(
- GlobalValues.ALIAS_CONTRACT_CERTIFICATE.toString(),
- contractCertPrivateKey,
- keyStorePassword.toCharArray(),
- getCertificateChain(contractCertChain));
-
- // Save the keystore persistently
- FileOutputStream fos = new FileOutputStream("evccKeystore.jks");
- keyStore.store(fos, GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString().toCharArray());
- fos.close();
-
- X509Certificate contractCert = getCertificate(contractCertChain.getCertificate());
-
- getLogger().info("Contract certificate with distinguished name '" +
- contractCert.getSubjectX500Principal().getName() + "' saved. " +
- "Valid until " + contractCert.getNotAfter()
- );
- getLogger().debug("Decrypted private key belonging to contract certificate saved. Key bytes: " +
- ByteUtils.toHexString(contractCertPrivateKey.getEncoded()));
- } else {
- getLogger().error("Private key for contract certificate is not valid");
- return false;
- }
- } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException | NullPointerException e) {
- getLogger().error(e.getClass().getSimpleName() + " occurred while trying to save contract " +
- "certificate chain", e);
- return false;
- }
-
- return true;
- }
-
-
- /**
- * Checks if the private key is a valid key (according to requirement [V2G2-823]) for the received contract
- * certificate before saving it to the keystore.
- * @param privateKey The private key corresponding to the contract certificate
- * @param contractCertChain The received contract certificate chain
- * @return True, if the private key is a valid key, false otherwise.
- */
- private static boolean isPrivateKeyValid(ECPrivateKey privateKey, CertificateChainType contractCertChain) {
- AlgorithmParameters parameters;
-
- try {
- parameters = AlgorithmParameters.getInstance("EC");
- parameters.init(new ECGenParameterSpec("secp256r1"));
-
- ECParameterSpec ecParameterSpec = parameters.getParameterSpec(ECParameterSpec.class);
-
- // Now we need to check if the private key is correct (see requirement [V2G2-823])
- BigInteger order = ecParameterSpec.getOrder();
- ECPoint basePoint = ecParameterSpec.getGenerator();
- BigInteger privateKeyValue = privateKey.getS();
- X509Certificate contractCert = getCertificate(contractCertChain.getCertificate());
- ECPublicKey publicKey = (ECPublicKey) contractCert.getPublicKey();
-
- // 1. check
- if (privateKeyValue.compareTo(order) != -1) {
- getLogger().error("Validation of private key failed: its value is not strictly smaller than the "
- + "order of the base point");
- return false;
- }
-
- // 2. check
- /*
- * TODO:
- * No idea how to check for
- * "multiplication of the base point with this value must generate a key matching the public key of
- * the contract certificate"
- * "this value" = value of private key
- * -> some more expert knowledge on the arithmetic of elliptic curves is needed to tackle this!
- */
-
- } catch (NoSuchAlgorithmException | InvalidParameterSpecException e) {
- getLogger().error(e.getClass().getSimpleName() + " occurred when trying to get private key from raw bytes", e);
- return false;
- }
-
- return true;
- }
-
- /**
- * Gets the contract certificate from the EVCC keystore.
- *
- * @return The contract certificate if present, null otherwise
- */
- public static X509Certificate getContractCertificate() {
- X509Certificate contractCertificate = null;
-
- KeyStore evccKeyStore = getKeyStore(
- GlobalValues.EVCC_KEYSTORE_FILEPATH.toString(),
- GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString()
- );
-
- try {
- contractCertificate = (X509Certificate) evccKeyStore.getCertificate(GlobalValues.ALIAS_CONTRACT_CERTIFICATE.toString());
- } catch (KeyStoreException e) {
- getLogger().error("KeyStoreException occurred while trying to get contract certificate from keystore", e);
- }
-
- return contractCertificate;
- }
-
-
- /**
- * A convenience function which checks if a contract certificate installation is needed.
- * Normally not needed because of function getContractCertificateStatus().
- *
- * @return True, if no contract certificate is store or if the stored certificate is not valid, false otherwise
- */
- public static boolean isContractCertificateInstallationNeeded() {
- X509Certificate contractCert = getContractCertificate();
-
- if (contractCert == null) {
- getLogger().info("No contract certificate stored");
- return true;
- } else if (!verifyValidityPeriod(contractCert).equals(ResponseCodeType.OK)) {
- return true;
- } else return false;
- }
-
-
- /**
- * A convenience function which checks if a contract certificate update is needed.
- * Normally not needed because of function getContractCertificateStatus().
- *
- * @return True, if contract certificate is still valid but about to expire, false otherwise.
- * The expiration period is given in GlobalValues.CERTIFICATE_EXPIRES_SOON_PERIOD.
- */
- public static boolean isContractCertificateUpdateNeeded() {
- X509Certificate contractCert = getContractCertificate();
- short validityOfContractCert = getValidityPeriod(contractCert);
-
- if (validityOfContractCert < 0) {
- getLogger().warn("Contract certificate with distinguished name '" +
- contractCert.getSubjectX500Principal().getName() + "' is not valid any more, expired " +
- Math.abs(validityOfContractCert) + " days ago");
- return false;
- } else if (validityOfContractCert <= GlobalValues.CERTIFICATE_EXPIRES_SOON_PERIOD.getShortValue()) {
- getLogger().info("Contract certificate with distinguished name '" +
- contractCert.getSubjectX500Principal().getName() + "' is about to expire in " +
- validityOfContractCert + " days");
- return true;
- } else return false;
- }
-
-
- /**
- * Checks whether a contract certificate
- * - is stored
- * - in case it is stored, if it is valid
- * - in case it is valid, if it expires soon
- *
- * This method is intended to reduce cryptographic computation overhead by checking both, if installation or
- * update is needed, at the same time. When executing either method by itself (isContractCertificateUpdateNeeded() and
- * isContractCertificateInstallationNeeded()), each time the certificate is read anew from the Java keystore
- * holding the contract certificate. With this method the contract certificate is read just once from the keystore.
- *
- * @return An enumeration value ContractCertificateStatus (either UPDATE_NEEDED, INSTALLATION_NEEDED, or OK)
- */
- public static ContractCertificateStatus getContractCertificateStatus() {
- X509Certificate contractCert = getContractCertificate();
-
- if (contractCert == null) {
- getLogger().info("No contract certificate stored");
- return ContractCertificateStatus.INSTALLATION_NEEDED;
- } else if (contractCert != null && !verifyValidityPeriod(contractCert).equals(ResponseCodeType.OK)) {
- return ContractCertificateStatus.INSTALLATION_NEEDED;
- } else {
- short validityOfContractCert = getValidityPeriod(contractCert);
- // Checking for a negative value of validityOfContractCert is not needed because the method
- // isCertificateValid() already checks for that
- if (validityOfContractCert <= GlobalValues.CERTIFICATE_EXPIRES_SOON_PERIOD.getShortValue()) {
- getLogger().info("Contract certificate with distinguished name '" +
- contractCert.getSubjectX500Principal().getName() + "' is about to expire in " +
- validityOfContractCert + " days");
- return ContractCertificateStatus.UPDATE_NEEDED;
- }
- return ContractCertificateStatus.OK;
- }
- }
-
-
- /**
- * Returns a list of certificates from the given CertificateChainType with the leaf certificate
- * being the first element and potential subcertificates (intermediate CA certificatess)
- * in the array of certificates.
- *
- * @param certChainType The CertificateChainType instance which holds a leaf certificate and
- * possible intermediate certificates to verify the leaf certificate up to
- * some root certificate.
- * @return An array of Certificates
- */
- public static Certificate[] getCertificateChain(CertificateChainType certChainType) {
- List<byte[]> subCertificates = certChainType.getSubCertificates().getCertificate();
- Certificate[] certChain = new Certificate[subCertificates.size() + 1];
-
- certChain[0] = getCertificate(certChainType.getCertificate());
-
- for (int i = 0; i < subCertificates.size(); i++) {
- certChain[i+1] = getCertificate(subCertificates.get(i));
- }
-
- return certChain;
- }
-
-
-
- /**
- * Generates an elliptic curve key pair using the named curve "secp256r1".
- * This function is mainly used for the ECDH procedure.
- *
- * To use ECC (elliptic curve cryptography), SECC as well as EVCC must agree on all the elements
- * defining the elliptic curve, that is, the "domain parameters" of the scheme. Such domain
- * parameters are predefined by standardization bodies and are commonly known as "standard curves"
- * or "named curves"; a named curve can be referenced either by name or by the unique object
- * identifier defined in the standard documents. For the ISO/IEC 15118-2 document, the named curve
- * "secp256r1" (SECG notation, see http://www.secg.org/sec2-v2.pdf) is used.
- * See [V2G2-818] in ISO/IEC 15118-2 for further information.
- *
- * @return An elliptic curve key pair according to the named curve 'secp256r1'
- */
- public static KeyPair getECKeyPair() {
- KeyPair keyPair = null;
-
- try {
- KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC");
- ECGenParameterSpec ecParameterSpec = new ECGenParameterSpec("secp256r1");
- keyPairGenerator.initialize(ecParameterSpec, new SecureRandom());
- keyPair = keyPairGenerator.generateKeyPair();
- } catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException e) {
- getLogger().error(e.getClass().getSimpleName() + " occurred while trying to generate ECDH key pair", e);
- }
-
- return keyPair;
- }
-
-
- /**
- * The shared secret is computed using the domain parameters of the named curve "secp256r1", the private key
- * part of the ephemeral key pair, and the OEM provisioning certiicate’s public key (in case of certificate
- * installation) or the contract certificate's public key (in case of certificate update).
- * The shared secret is used as input to a key derivation function.
- * A key derivation function (KDF) is a deterministic algorithm to derive a key of a given
- * size from some secret value. If two parties use the same shared secret value and the same KDF,
- * they should always derive exactly the same key.
- *
- * @param privateKey The private key of an EC key pair generated from the named curve "secp256r1".
- *
- * The mobility operator (MO) provides his ephemeral private key when using this function for
- * generating the shared secret to encrypt the private key of the contract certificate.
- *
- * The EVCC provides the private key belonging to his OEM provisioning certificate's public key
- * when using this function for generating the shared secret to decrypt the encrypted private key
- * of the newly to be installed contract certificate.
- * @param publicKey The public key of an EC key pair generated from the named curve "secp256r1"
- *
- * The mobility operator (MO) provides the static OEM provisioning certificate's (in case of
- * CertificateInstallation) or old contract certificate's (in case of CertificateUpdate)
- * public key when using this function for generating the shared secret to encrypt the private
- * key of the contract certificate.
- *
- * The EVCC provides the ephemeral public key of the MO (coming with the CertificateInstallationRes
- * or CertificateUpdateRes, respectively) when using this function for generating the shared secret
- * to decrypt the encrypted private key of the newly to be installed contract certificate.
- * @return The computed shared secret of the elliptic curve Diffie-Hellman key exchange protocol
- */
- public static byte[] generateSharedSecret(ECPrivateKey privateKey, ECPublicKey publicKey) {
- try {
- KeyAgreement keyAgreement = KeyAgreement.getInstance("ECDH");
- keyAgreement.init(privateKey, new SecureRandom());
- keyAgreement.doPhase(publicKey, true);
- return keyAgreement.generateSecret();
- } catch (InvalidKeyException | NoSuchAlgorithmException e) {
- getLogger().error(e.getClass().getSimpleName() + " occurred while trying to generate the shared secret (ECDH)", e);
- return null;
- }
- }
-
-
- /**
- * The key derivation function (KDF). See [V2G2-818] in ISO/IEC 15118-2 for further information.
- *
- * @param sharedSecret The shared secret derived from the ECDH algorithm
- */
- public static SecretKey generateSessionKey(byte[] sharedSecret) {
- MessageDigest md = null;
- /*
- * TODO it is unclear to me what should be the content of suppPubInfo or suppPrivInfo
- * according to page 49 of http://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Ar2.pdf
- * Requirement [V2G2-818] is not clear about that.
- */
- byte[] suppPubInfo = null;
- byte[] suppPrivInfo = null;
-
- try {
- md = MessageDigest.getInstance("SHA-256");
- } catch (NoSuchAlgorithmException e1) {
- getLogger().error("Message digest algorithm SHA-256 not supported");
- return null;
- }
-
- ByteArrayOutputStream baosOtherInfo = new ByteArrayOutputStream();
- try {
- baosOtherInfo.write(ByteUtils.toByteArrayFromHexString("01")); // algorithm ID
- baosOtherInfo.write(ByteUtils.toByteArrayFromHexString("55")); // partyUInfo
- baosOtherInfo.write(ByteUtils.toByteArrayFromHexString("56")); // partyVInfo
- if (suppPubInfo != null) baosOtherInfo.write(suppPubInfo);
- if (suppPrivInfo != null) baosOtherInfo.write(suppPrivInfo);
- } catch (IOException e) {
- getLogger().error("IOException occurred while trying to write OtherInfo for session key generation", e);
- }
-
- byte[] otherInfo = baosOtherInfo.toByteArray();
-
- // A symmetric encryption key of exactly 128 bits shall be derived.
- byte[] sessionKeyAsByteArray = concatKDF(md, sharedSecret, 128, otherInfo);
-
- SecretKey sessionKey = null;
- try {
- sessionKey = new SecretKeySpec(sessionKeyAsByteArray, "AES");
- } catch (IllegalArgumentException e) {
- getLogger().error("IllegalArgumentException occurred while trying to generate session key", e);
- }
-
- return sessionKey;
- }
-
-
- /**
- * Implementation of Concatenation Key Derivation Function
- * http://csrc.nist.gov/publications/nistpubs/800-56A/SP800-56A_Revision1_Mar08-2007.pdf
- *
- * Author: NimbusDS Lai Xin Chu and Vladimir Dzhuvinov
- *
- * See https://code.google.com/p/openinfocard/source/browse/trunk/testsrc/org/xmldap/crypto/ConcatKeyDerivationFunction.java?r=770
- */
- private static byte[] concatKDF(MessageDigest md, byte[] z, int keyDataLen, byte[] otherInfo) {
- final long MAX_HASH_INPUTLEN = Long.MAX_VALUE;
- final long UNSIGNED_INT_MAX_VALUE = 4294967295L;
- keyDataLen = keyDataLen/8;
- byte[] key = new byte[keyDataLen];
-
- int hashLen = md.getDigestLength();
- int reps = keyDataLen / hashLen;
-
- if (reps > UNSIGNED_INT_MAX_VALUE) {
- getLogger().error("Key derivation failed");
- return null;
- }
-
- int counter = 1;
- byte[] counterInBytes = ByteUtils.intToFourBytes(counter);
-
- if ((counterInBytes.length + z.length + otherInfo.length) * 8 > MAX_HASH_INPUTLEN) {
- getLogger().error("Key derivation failed");
- return null;
- }
-
- for (int i = 0; i <= reps; i++) {
- md.reset();
- md.update(ByteUtils.intToFourBytes(i+1));
- md.update(z);
- md.update(otherInfo);
-
- byte[] hash = md.digest();
- if (i < reps) {
- System.arraycopy(hash, 0, key, hashLen * i, hashLen);
- } else {
- if (keyDataLen % hashLen == 0) {
- System.arraycopy(hash, 0, key, hashLen * i, hashLen);
- } else {
- System.arraycopy(hash, 0, key, hashLen * i, keyDataLen % hashLen);
- }
- }
- }
-
- return key;
- }
-
- private static ContractSignatureEncryptedPrivateKeyType getContractSignatureEncryptedPrivateKey(
- SecretKey sessionKey, ECPrivateKey contractCertPrivateKey) {
- ContractSignatureEncryptedPrivateKeyType encryptedPrivateKey = new ContractSignatureEncryptedPrivateKeyType();
- encryptedPrivateKey.setValue(encryptPrivateKey(sessionKey, contractCertPrivateKey));
-
- return encryptedPrivateKey;
- }
-
-
- /**
- * Encrypts the private key of the contract certificate which is to be sent to the EVCC. First, the
- * shared secret based on the ECDH parameters is calculated, then the symmetric session key with which
- * the private key of the contract certificate is to be encrypted.
- *
- * @param certificateECPublicKey The public key of either the OEM provisioning certificate (in case of
- * CertificateInstallation) or the to be updated contract certificate
- * (in case of CertificateUpdate)
- * @param dhPrivateKey The DH private key
- * @param contractCertPrivateKey The private key of the contract certificate
- * @return The encrypted private key of the to be installed contract certificate
- */
- public static ContractSignatureEncryptedPrivateKeyType encryptContractCertPrivateKey(
- ECPublicKey certificateECPublicKey,
- ECPrivateKey dhPrivateKey,
- ECPrivateKey contractCertPrivateKey) {
- // Generate the shared secret by using the public key of either OEMProvCert or ContractCert
- byte[] sharedSecret = generateSharedSecret(dhPrivateKey, certificateECPublicKey);
-
- if (sharedSecret == null) {
- getLogger().error("Shared secret could not be generated");
- return null;
- }
-
- // The session key is generated using the computed shared secret
- SecretKey sessionKey = generateSessionKey(sharedSecret);
-
- // Finally, the private key of the contract certificate is encrypted using the session key
- ContractSignatureEncryptedPrivateKeyType encryptedContractCertPrivateKey =
- getContractSignatureEncryptedPrivateKey(sessionKey, contractCertPrivateKey);
-
- return encryptedContractCertPrivateKey;
- }
-
-
- /**
- * Applies the algorithm AES-CBC-128 according to NIST Special Publication 800-38A.
- * The initialization vector IV shall be randomly generated before encryption and shall have a
- * length of 128 bit and never be reused.
- * The IV shall be transmitted in the 16 most significant bytes of the
- * ContractSignatureEncryptedPrivateKey field.
- *
- * @param sessionKey The symmetric session key with which the private key will be encrypted
- * @param contractCertPrivateKey The private key which is to be encrypted
- * @return The encrypted private key of the contract certificate given as a byte array
- */
- private static byte[] encryptPrivateKey(SecretKey sessionKey, ECPrivateKey contractCertPrivateKey) {
- try {
- /*
- * Padding of the plain text (private key) is not required as its length (256 bit) is a
- * multiple of the block size (128 bit) of the used encryption algorithm (AES)
- */
- Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
- IvParameterSpec ivParamSpec = new IvParameterSpec(generateRandomNumber(16));
- cipher.init(Cipher.ENCRYPT_MODE, sessionKey, ivParamSpec);
-
- /*
- * Not the complete ECPrivateKey container, but the private value s represents the 256 bit
- * private key which must be encoded.
- * The private key is stored as an ASN.1 integer which may need to have zero padding
- * in the most significant bits removed (if 33 bytes)
- */
- byte[] encryptedKey;
- if (contractCertPrivateKey.getS().toByteArray().length == 33) {
- byte[] temp = new byte[32];
- System.arraycopy(contractCertPrivateKey.getS().toByteArray(), 1, temp, 0, contractCertPrivateKey.getS().toByteArray().length-1);
- encryptedKey = cipher.doFinal(temp);
- } else {
- encryptedKey = cipher.doFinal(contractCertPrivateKey.getS().toByteArray());
- }
-
- /*
- * The IV must be transmitted in the 16 most significant bytes of the
- * ContractSignatureEncryptedPrivateKey
- */
- byte[] encryptedKeyWithIV = new byte[ivParamSpec.getIV().length + encryptedKey.length];
- System.arraycopy(ivParamSpec.getIV(), 0, encryptedKeyWithIV, 0, ivParamSpec.getIV().length);
- System.arraycopy(encryptedKey, 0, encryptedKeyWithIV, ivParamSpec.getIV().length, encryptedKey.length);
- getLogger().debug("Encrypted private key: " + ByteUtils.toHexString(encryptedKeyWithIV));
-
- return encryptedKeyWithIV;
- } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException |
- InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
- getLogger().error(e.getClass().getSimpleName() + " occurred while trying to encrypt private key." +
- "\nSession key (" + sessionKey.getEncoded().length + " bytes): " +
- ByteUtils.toHexString(sessionKey.getEncoded()) +
- "\nContract certificate private key (" + contractCertPrivateKey.getS().toByteArray().length + " bytes): " +
- ByteUtils.toHexString(contractCertPrivateKey.getS().toByteArray()), e);
- }
-
- return null;
- }
-
-
- /**
- * Decrypts the encrypted private key of the contract certificate which is to be installed.
- *
- * @param dhPublicKey The ECDH public key received the the respective response message
- * (either CertificateInstallationRes or CertificateUpdateRes)
- * @param contractSignatureEncryptedPrivateKey The encrypted private key of the contract certificate
- * @param certificateECPrivateKey The private key of either OEMProvisioningCertificate (in case of
- * receipt of CertificateInstallationRes) or the existing ContractCertificate which is to be
- * updated (in case of receipt of CertificateUpdateRes).
- * @return The decrypted private key of the contract certificate which is to be installed
- */
- public static ECPrivateKey decryptContractCertPrivateKey(
- byte[] dhPublicKey,
- byte[] contractSignatureEncryptedPrivateKey,
- ECPrivateKey certificateECPrivateKey) {
- // Generate shared secret
- ECPublicKey publicKey = getPublicKey(dhPublicKey);
- byte[] sharedSecret = generateSharedSecret(certificateECPrivateKey, publicKey);
- if (sharedSecret == null) {
- getLogger().error("Shared secret could not be generated");
- return null;
- }
-
- // Generate the session key ...
- SecretKey sessionKey = generateSessionKey(sharedSecret);
- if (sessionKey == null) {
- getLogger().error("Session key secret could not be generated");
- return null;
- }
-
- // ... to decrypt the contract certificate private key
- ECPrivateKey contractCertPrivateKey = decryptPrivateKey(sessionKey, contractSignatureEncryptedPrivateKey);
- if (contractCertPrivateKey == null) {
- getLogger().error("Contract certificate private key secret could not be decrypted");
- return null;
- }
-
- return contractCertPrivateKey;
- }
-
-
- /**
- * The private key corresponding to the contract certificate is to be decrypted by
- * the receiver (EVCC) using the session key derived in the ECDH protocol.
- * Applies the algorithm AES-CBC-128 according to NIST Special Publication 800-38A.
- * The initialization vector IV shall be read from the 16 most significant bytes of the
- * ContractSignatureEncryptedPrivateKey field.
- *
- * @param sessionKey The symmetric session key with which the encrypted private key is to be decrypted
- * @param encryptedKeyWithIV The encrypted private key of the contract certificate given as a byte array
- * whose first 16 byte hold the initialization vector
- * @return The decrypted private key of the contract certificate
- */
- private static ECPrivateKey decryptPrivateKey(SecretKey sessionKey, byte[] encryptedKeyWithIV) {
- byte[] initVector = new byte[16];
- byte[] encryptedKey = null;
-
- try {
- // Get the first 16 bytes of the encrypted private key which hold the IV
-
- encryptedKey = new byte[encryptedKeyWithIV.length - 16];
- System.arraycopy(encryptedKeyWithIV, 0, initVector, 0, 16);
- System.arraycopy(encryptedKeyWithIV, 16, encryptedKey, 0, encryptedKeyWithIV.length - 16);
-
- IvParameterSpec ivParamSpec = new IvParameterSpec(initVector);
- Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
-
- /*
- * You must have the Java Cryptography Extension (JCE) Unlimited Strength
- * Jurisdiction Policy Files 8 installed, otherwise this cipher.init call will yield a
- * "java.security.InvalidKeyException: Illegal key size"
- */
- cipher.init(Cipher.DECRYPT_MODE, sessionKey, ivParamSpec);
- byte[] decrypted = cipher.doFinal(encryptedKey);
- return getPrivateKey(decrypted);
- } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException |
- InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException |
- NegativeArraySizeException e) {
- getLogger().error(e.getClass().getSimpleName() + " occurred while trying to decrypt private key" +
- "\nSession key (" + (sessionKey != null ? sessionKey.getEncoded().length : 0) + " bytes): " +
- ByteUtils.toHexString(sessionKey.getEncoded()) +
- "\nEncrypted key (" + (encryptedKey != null ? encryptedKey.length : 0) + " bytes): " +
- ByteUtils.toHexString(encryptedKey) +
- "\nEncrypted key with IV (" + (encryptedKeyWithIV != null ? encryptedKeyWithIV.length : 0) + " bytes): " +
- ByteUtils.toHexString(encryptedKey), e);
- }
-
- return null;
- }
-
-
- /**
- * Useful for debugging purposes when verifying a signature and trying to figure out where it went wrong if
- * a signature verification failed.
- *
- * @return
- */
- // public static byte[] decryptSignature(byte[] signature, ECPublicKey publicKey) {
- //
- // }
-
-
- /**
- * Returns the EMAID (e-mobility account identifier) from the contract certificate as part of the contract certificate chain.
- *
- * @param contractCertificateChain The certificate chain holding the contract certificate
- * @return The EMAID
- */
- public static EMAIDType getEMAID(CertificateChainType contractCertificateChain) {
- X509Certificate contractCertificate = getCertificate(contractCertificateChain.getCertificate());
- return getEMAIDFromDistinguishedName(contractCertificate.getSubjectX500Principal().getName());
- }
-
-
- /**
- * Returns the EMAID (e-mobility account identifier) from the contract certificate.
- *
- * @param contractCertificate The contract certificate
- * @return The EMAID
- */
- public static EMAIDType getEMAID(X509Certificate contractCertificate) {
- return getEMAIDFromDistinguishedName(contractCertificate.getSubjectX500Principal().getName());
- }
-
-
- /**
- * Returns the EMAID (e-mobility account identifier) from the contract certificate.
- *
- * @param keyStorePassword The password which protects the keystore holding the contract certificate
- * @return The EMAID
- */
- public static EMAIDType getEMAID(String keyStorePassword) {
- KeyStore keyStore = getKeyStore(GlobalValues.EVCC_KEYSTORE_FILEPATH.toString(), keyStorePassword);
-
- try {
- X509Certificate contractCertificate =
- (X509Certificate) keyStore.getCertificate(GlobalValues.ALIAS_CONTRACT_CERTIFICATE.toString());
-
- if (contractCertificate == null) {
- getLogger().error("No contract certificate with alias '" +
- GlobalValues.ALIAS_CONTRACT_CERTIFICATE.toString() + "' found");
- return null;
- }
-
- return getEMAIDFromDistinguishedName(contractCertificate.getSubjectX500Principal().getName());
- } catch (KeyStoreException e) {
- getLogger().error("KeyStoreException occurred while trying to get EMAID from keystore", e);
- return null;
- }
- }
-
-
- /**
- * Reads the EMAID (e-mobility account identifier) from the distinguished name (DN) of a certificate.
- *
- * @param distinguishedName The distinguished name whose 'CN' component holds the EMAID
- * @return The EMAID
- */
- private static EMAIDType getEMAIDFromDistinguishedName(String distinguishedName) {
- EMAIDType emaid = new EMAIDType();
-
- LdapName ln = null;
- try {
- ln = new LdapName(distinguishedName);
- } catch (InvalidNameException e) {
- getLogger().error("InvalidNameException occurred while trying to get EMAID from distinguished name", e);
- }
- for(Rdn rdn : ln.getRdns()) {
- if (rdn.getType().equalsIgnoreCase("CN")) {
- // Optional hyphens used for better human readability must be omitted here
- emaid.setId("id1");
- emaid.setValue(rdn.getValue().toString().replace("-", ""));
-
- break;
- }
- }
-
- return emaid;
- }
-
-
- /**
- * Searches a given keystore either for a contract certificate chain or OEM provisioning certificate
- * chain, determined by the alias (the alias is associated with the certificate chain and the private
- * key).
- * However, it may be the case that more than once contract certificate is installed in the EV,
- * in which case an OEM specific implementation would need to interact at this point with a HMI in
- * order to enable the user to select the certificate which is to be used for contract based charging.
- *
- * @param evccKeyStore The keystore to check for the respective certificate chain
- * @param alias The alias associated with a key entry and certificate chain
- * @return The respective certificate chain if present, null otherwise
- */
- public static CertificateChainType getCertificateChain(KeyStore evccKeyStore, String alias) {
- CertificateChainType certChain = new CertificateChainType();
- SubCertificatesType subCertificates = new SubCertificatesType();
-
- try {
- Certificate[] certChainArray = evccKeyStore.getCertificateChain(alias);
-
- if (certChainArray == null) {
- getLogger().info("No certificate chain found for alias '" + alias + "'");
- return null;
- }
-
- certChain.setCertificate(certChainArray[0].getEncoded());
- for (int i = 1; i < certChainArray.length; i++) {
- subCertificates.getCertificate().add(certChainArray[i].getEncoded());
- }
-
- certChain.setSubCertificates(subCertificates);
-
- return certChain;
- } catch (KeyStoreException | CertificateEncodingException e) {
- getLogger().error(e.getClass().getSimpleName() + " occurred while trying to get certificate chain", e);
- return null;
- }
- }
-
-
- /**
- * Returns a random number of a given length of bytes.
- *
- * @param lengthOfBytes The number of bytes which hold the generated random number
- * @return A random number given as a byte array
- */
- public static byte[] generateRandomNumber(int lengthOfBytes) {
- // TODO how to assure that the entropy of the genChallenge is at least 120 bits according to [V2G2-826]?
-
- SecureRandom random = new SecureRandom();
- byte[] randomNumber = new byte[lengthOfBytes];
- random.nextBytes(randomNumber);
-
- return randomNumber;
- }
-
-
- /**
- * Generates a digest for a complete message or field (which ever is handed over as first parameter).
- * During digest (SHA-256) generation, the parameter is converted to a JAXBElement and then EXI encoded
- * using the respective EXI schema-informed grammar. If the digest for the signature is to be generated,
- * the second parameter is to be set to true, for all other messages or fields the second parameter
- * needs to be set to false.
- *
- * @param jaxbMessageOrField The message or field for which a digest is to be generated, given as a JAXB element
- * @param digestForSignedInfoElement True if a digest for the SignedInfoElement of the header's signature is to be generated, false otherwise
- * @return The SHA-256 digest for message or field
- */
- @SuppressWarnings("rawtypes")
- public static byte[] generateDigest(String id, JAXBElement jaxbMessageOrField) {
- byte[] encoded;
-
- // The schema-informed fragment grammar option needs to be used for EXI encodings in the header's signature
- getExiCodec().setFragment(true);
-
- /*
- * When creating the signature value for the SignedInfoElement, we need to use the XMLdsig schema,
- * whereas for creating the reference elements of the signature, we need to use the V2G_CI_MsgDef schema.
- */
- if (jaxbMessageOrField.getValue() instanceof SignedInfoType) {
- encoded = getExiCodec().encodeEXI(jaxbMessageOrField, GlobalValues.SCHEMA_PATH_XMLDSIG.toString());
- } else encoded = getExiCodec().encodeEXI(jaxbMessageOrField, GlobalValues.SCHEMA_PATH_MSG_DEF.toString());
-
- // Do not use the schema-informed fragment grammar option for other EXI encodings (message bodies)
- getExiCodec().setFragment(false);
-
- if (encoded == null) {
- getLogger().error("Digest could not be generated because of EXI encoding problem");
- return null;
- }
-
- try {
- MessageDigest md = MessageDigest.getInstance("SHA-256");
- md.update(encoded);
- byte[] digest = md.digest();
-
- if (showSignatureVerificationLog) {
- /*
- * Show Base64 encoding of digests only for reference elements, not for the SignedInfo element.
- * The hashed SignedInfo element is input for ECDSA before the final signature value gets Base64 encoded.
- */
- if ( !(jaxbMessageOrField.getValue() instanceof SignedInfoType) ) {
- getLogger().debug("\n"
- + "\tDigest generated for XML reference element " + jaxbMessageOrField.getName().getLocalPart() + " with ID '" + id + "': " + ByteUtils.toHexString(digest) + "\n"
- + "\tBase64 encoding of digest: " + Base64.getEncoder().encodeToString(digest));
- }
- }
-
- return digest;
- } catch (NoSuchAlgorithmException e) {
- getLogger().error("NoSuchAlgorithmException occurred while trying to create digest", e);
- return null;
- }
- }
-
-
- /**
- * Signs the SignedInfo element of the V2GMessage header.
- *
- * @param signedInfoElementExi The EXI-encoded SignedInfo element given as a byte array
- * @param ecPrivateKey The private key which is used to sign the SignedInfo element
- * @return The signature value for the SignedInfo element given as a byte array
- */
- public static byte[] signSignedInfoElement(byte[] signedInfoElementExi, ECPrivateKey ecPrivateKey) {
- try {
- Signature ecdsa = Signature.getInstance("SHA256withECDSA", "SunEC");
-
- getLogger().debug("EXI encoded SignedInfo: " + ByteUtils.toHexString(signedInfoElementExi));
-
- if (ecPrivateKey != null) {
- getLogger().debug("\n\tPrivate key used for creating signature: " + ByteUtils.toHexString(ecPrivateKey.getS().toByteArray()));
-
- ecdsa.initSign(ecPrivateKey);
- ecdsa.update(signedInfoElementExi);
-
- byte[] signature = ecdsa.sign();
-
- // Java operates on DER encoded signatures, but we must send the raw r and s values as signature
- byte[] rawSignature = getRawSignatureFromDEREncoding(signature);
-
- getLogger().debug("Signature value: " + ByteUtils.toHexString(rawSignature));
-
- return rawSignature;
- } else {
- getLogger().error("Private key used to sign SignedInfo element is null");
- return null;
- }
- } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException | NoSuchProviderException e) {
- getLogger().error(e.getClass().getSimpleName() + " occurred while trying to create signature", e);
- return null;
- }
- }
-
- /**
- * Verifies the signature given in the received header of an EVCC or SECC message
- *
- * @param signature The received header's signature
- * @param jaxbSignature The received header's signature, given as a JAXB element (needed for EXI operations)
- * @param verifyXMLSigRefElements The HashMap of signature IDs and digest values of the message body
- * or fields respectively of the received message (to cross-check against the XML reference
- * elements contained in the received message header)
- * @param verifyCert The certificate holding the public key corresponding to the private key which was used
- * for the signature. Given as a byte array, this function will call verifySignature() with an X509Certificate
- * as last parameter.
- * @return True, if digest validation of all XML reference elements and signature validation was
- * successful, false otherwise
- */
- public static boolean verifySignature(
- SignatureType signature,
- JAXBElement<SignedInfoType> jaxbSignature,
- HashMap<String, byte[]> verifyXMLSigRefElements,
- byte[] verifyCert) {
- X509Certificate x509VerifyCert = getCertificate(verifyCert);
- return verifySignature(signature, jaxbSignature, verifyXMLSigRefElements, x509VerifyCert);
- }
-
- /**
- * Verifies the signature given in the received header of an EVCC or SECC message
- *
- * @param signature The received header's signature
- * @param jaxbSignature The received header's signature, given as a JAXB element (needed for EXI operations)
- * @param verifyXMLSigRefElements The HashMap of signature IDs and digest values of the message body
- * or fields respectively of the received message (to cross-check against the XML reference
- * elements contained in the received message header)
- * @param verifyCert The certificate holding the public key corresponding to the private key which was used for the signature
- * @return True, if digest validation of all XML reference elements and signature validation was
- * successful, false otherwise
- */
- public static boolean verifySignature(
- SignatureType signature,
- JAXBElement<SignedInfoType> jaxbSignedInfo,
- HashMap<String, byte[]> verifyXMLSigRefElements,
- X509Certificate verifyCert) {
- byte[] calculatedReferenceDigest;
- boolean messageDigestsEqual;
-
- /*
- * 1. step:
- * Iterate over all element IDs of the message which should have been signed and find the
- * respective Reference element in the given message header
- */
- for (String id : verifyXMLSigRefElements.keySet()) {
- getLogger().debug("Verifying digest for element '" + id + "'");
- messageDigestsEqual = false;
- calculatedReferenceDigest = verifyXMLSigRefElements.get(id);
-
- for (ReferenceType reference : signature.getSignedInfo().getReference()) {
- if (reference == null) {
- getLogger().warn("Reference element to check is null");
- continue;
- }
-
- // We need to check the URI attribute, not the Id attribute. But the Id must be set to sth. different than the IDs used in the body!
- if (reference.getURI() == null) {
- getLogger().warn("Reference ID element is null");
- continue;
- }
-
- if (reference.getURI().equals('#' + id)) {
- messageDigestsEqual = MessageDigest.isEqual(reference.getDigestValue(), calculatedReferenceDigest);
-
- if (showSignatureVerificationLog) {
- getLogger().debug("\n"
- + "\tReceived digest of reference with ID '" + id + "': " + ByteUtils.toHexString(reference.getDigestValue()) + "\n"
- + "\tCalculated digest of reference with ID '" + id + "': " + ByteUtils.toHexString(calculatedReferenceDigest) + "\n"
- + "\t==> Match: " + messageDigestsEqual);
- }
-
- }
- }
-
- if (!messageDigestsEqual) {
- getLogger().error("No matching signature found for ID '" + id + "' and digest value " +
- ByteUtils.toHexString(calculatedReferenceDigest));
- return false;
- }
- }
-
-
- /*
- * 2. step:
- * Check the signature itself
- */
- ECPublicKey ecPublicKey = (ECPublicKey) verifyCert.getPublicKey();
- Signature ecdsa;
- boolean verified;
-
- try {
- getLogger().debug("Verifying signature of SignedInfo element ...");
-
- // Check if signature verification logging is to be shown (for debug purposes)
-
- if (showSignatureVerificationLog) showSignatureVerificationLog(verifyCert, signature, jaxbSignedInfo, ecPublicKey);
-
- ecdsa = Signature.getInstance("SHA256withECDSA");
- // The Signature object needs to be initialized by setting it into the VERIFY state with the public key
- ecdsa.initVerify(ecPublicKey);
-
- // The data to be signed needs to be supplied to the Signature object
- byte[] exiEncodedSignedInfo = getExiCodec().getExiEncodedSignedInfo(jaxbSignedInfo);
- ecdsa.update(exiEncodedSignedInfo);
-
- // Java operates on DER encoded signature values, but the sent signature consists of the raw r and s value
- byte[] signatureValue = signature.getSignatureValue().getValue();
- byte[] derEncodedSignatureValue = getDEREncodedSignature(signatureValue);
-
- // The verify() method will do both, the decryption and SHA256 validation. So don't hash separately before verifying
- verified = ecdsa.verify(derEncodedSignatureValue);
-
- return verified;
- } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
- getLogger().error(e.getClass().getSimpleName() + " occurred while trying to verify signature value", e);
- return false;
- }
- }
-
-
- /**
- * Shows some extended logging while verifying a signature for debugging purposes.
- * @param verifyCert The X509Certificate whose public key is used to verify the signature, used for printing the
- * certificate's subject value
- * @param signature The signature contained in the header of the V2GMessage
- * @param ecPublicKey The public key used to verify the signature
- */
- private static void showSignatureVerificationLog(
- X509Certificate verifyCert,
- SignatureType signature,
- JAXBElement<SignedInfoType> jaxbSignedInfo,
- ECPublicKey ecPublicKey) {
- byte[] computedSignedInfoDigest = generateDigest("", jaxbSignedInfo);
- byte[] receivedSignatureValue = signature.getSignatureValue().getValue();
-
- getLogger().debug("\n"
- + "\tCertificate used to verify signature: " + verifyCert.getSubjectX500Principal().getName() + "\n"
- + "\tPublic key used to verify signature: " + ByteUtils.toHexString(getUncompressedSubjectPublicKey(ecPublicKey)) + "\n"
- + "\tReceived signature value: " + ByteUtils.toHexString(receivedSignatureValue) + " (Base64: " + Base64.getEncoder().encodeToString(receivedSignatureValue) + ")\n"
- + "\tCalculated digest of SignedInfo element: " + ByteUtils.toHexString(computedSignedInfoDigest));
- }
-
- /**
- * Java puts some encoding information into the ECPublicKey.getEncoded().
- * This method returns the raw ECPoint (the x and y coordinate of the public key) in uncompressed form
- * (with the 0x04 as first octet), aka the Subject Public Key according to RFC 5480
- *
- * @param ecPublicKey The ECPublicKey provided by Java
- * @return The uncompressed Subject Public Key (with the first octet set to 0x04)
- */
- public static byte[] getUncompressedSubjectPublicKey(ECPublicKey ecPublicKey) {
- byte[] uncompressedPubKey = new byte[65];
- uncompressedPubKey[0] = 0x04;
-
- byte[] affineX = ecPublicKey.getW().getAffineX().toByteArray();
- byte[] affineY = ecPublicKey.getW().getAffineY().toByteArray();
-
- // If the length is 33 bytes, then the first byte is a 0x00 which is to be omitted
- if (affineX.length == 33)
- System.arraycopy(affineX, 1, uncompressedPubKey, 1, 32);
- else
- System.arraycopy(affineX, 0, uncompressedPubKey, 1, 32);
-
- if (affineY.length == 33)
- System.arraycopy(affineY, 1, uncompressedPubKey, 33, 32);
- else
- System.arraycopy(affineY, 0, uncompressedPubKey, 33, 32);
-
- return uncompressedPubKey;
- }
-
-
- /**
- * An ECDSA signature consists of two positive integers r and s, each of the bit length equal to the curve size.
- * When Java is creating an ECDSA signature, it is encoding it in the DER (Distinguished Encoding Rules) format.
- * But in ISO 15118, we do not expect DER encoded signatures. Thus, this function takes the DER encoded signature
- * as input and returns the raw r and s integer values of the signature.
- * See further explanations in the @getDEREncodedSignature function for DER encoded ECDSA signatures.
- *
- * @param derEncodedSignature The DER encoded signature as a result from java.security.Signature.sign()
- * @return A byte array containing only the r and s value of the signature
- */
- public static byte[] getRawSignatureFromDEREncoding(byte[] derEncodedSignature) {
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- byte[] r = new byte[32];
- byte[] s = new byte[32];
-
- // Length of r is encoded in the fourth byte
- int lengthOfR = derEncodedSignature[3];
-
- // Length of r is encoded in the second byte AFTER r
- int lengthOfS = derEncodedSignature[lengthOfR + 5];
-
- // Length of r and s are either 33 bytes (including padding byte 0x00), 32 bytes (normal), or less (leftmost 0x00 bytes were removed)
- try {
- if (lengthOfR == 33) System.arraycopy(derEncodedSignature, 5, r, 0, lengthOfR - 1); // skip leftmost padding byte 0x00
- else if (lengthOfR == 32) System.arraycopy(derEncodedSignature, 4, r, 0, lengthOfR);
- else System.arraycopy(derEncodedSignature, 4, r, 32 - lengthOfR, lengthOfR); // destPos = number of leftmost 0x00 bytes
-
- if (lengthOfS == 33) System.arraycopy(derEncodedSignature, lengthOfR + 7, s, 0, lengthOfS - 1); // skip leftmost padding byte 0x00
- else if (lengthOfS == 32) System.arraycopy(derEncodedSignature, lengthOfR + 6, s, 0, lengthOfS);
- else System.arraycopy(derEncodedSignature, lengthOfR + 6, s, 32 - lengthOfS, lengthOfS); // destPos = number of leftmost 0x00 bytes
- } catch (ArrayIndexOutOfBoundsException e) {
- getLogger().error("ArrayIndexOutOfBoundsException occurred while trying to get raw signature from DER encoded signature.", e);
- }
-
- try {
- baos.write(r);
- baos.write(s);
- } catch (IOException e) {
- getLogger().error("IOException occurred while trying to write r and s into DER-encoded signature", e);
- }
-
- byte[] rawRAndS = baos.toByteArray();
-
- if (showSignatureVerificationLog) {
- StringBuilder sb = new StringBuilder();
- sb.append("Signature encoding DER -> raw:").append(System.lineSeparator());
- sb.append("\tDER: ").append(ByteUtils.toHexString(derEncodedSignature)).append(System.lineSeparator());
- sb.append("\tR: ").append(ByteUtils.toHexString(r)).append(System.lineSeparator());
- sb.append("\tS: ").append(ByteUtils.toHexString(s)).append(System.lineSeparator());
- sb.append("\tRaw: ").append(ByteUtils.toHexString(rawRAndS));
- getLogger().debug(sb.toString());
- }
-
- return rawRAndS;
- }
-
-
- /**
- * When encoded in DER, the signature - holding the
- * x-coordinate of the elliptic curve point in the value "r"
- * and the
- * y-coordinate of the elliptic curve point in the value "s"
- * - becomes the following sequence of bytes (in total somewhere between 68 and 72 bytes instead of 64 bytes):
- *
- * 0x30 len(z) 0x02 len(r) r 0x02 len(s) s
- *
- * where:
- *
- * - 0x30: is always the first byte of the DER encoded signature format (ASN.1 tag for sequence)
- *
- * - len(z): is a single byte value, encoding the length in bytes of the sequence z (remaining list of bytes)
- * (from the first 0x02 to the end of the encoding); is a value between 0x43 and 0x46
- *
- * - 0x02: is a fixed value indicating that an integer value will follow (ASN.1 tag for int)
- *
- * - len(r): is a single byte value, encoding the length in bytes of r;
- * Distinguished Encoding Rules (DER)-encoded integers are defined so that they can encode both positive and negative values
- * (aka signed values). This means that the leftmost bit (aka most-significant bit in big-endian) indicates whether the value
- * is positive (0) or negative (1).
- * 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
- * padding byte must be added.
- *
- * Furthermore, DER require that integer values are represented in the shortest byte representation possible. This
- * effectively prohibits the use of leading zeroes (0x00) if the leftmost bit was not set to 1.
- *
- * So len(r) will either be 0x21 (33 bytes), 0x20 (32 bytes) or less (mostly not less than 0x1F (31 bytes)).
- * Case 31 bytes or less: The leftmost bytes of the raw (non-DER-encoded) r are 0x00 and, according to DER, need to be
- * removed so that r is DER-encoded in the shortest possible way. Also, the leftmost bit of the
- * remaining byte array is 0 (-> a positive x-value).
- * Case 32 bytes: What we would normally expect, as the x- and y-coordinates are positive values of 32 bytes length.
- * The leftmost bit is set to 0 and the leftmost byte is not 0x00.
- * Case 33 bytes: A padding 0x00 byte was added as the most significant (leftmost) byte because the raw (non-DER-encoded) r
- * value had the leftmost bit set to 1, which would result in a negative value.
- *
- * - r: is the signed big-endian encoding of the value "r", of minimal length;
- *
- * - 0x02: is a fixed value indicating that an integer value will follow (ASN.1 tag for int)
- *
- * - len(s): is a single byte value, encoding the length in bytes of s;
- * (See further explanation of len(r) that applies as well for len(s))
- *
- * - s: is the signed big-endian encoding of the value "s", of minimal length.
- *
- * @param rawSignatureValue The r and s values (each 32 bytes) of an ECDSA signature, given as a byte array of 64 bytes
- * @return A byte array representing the DER-encoded version of the raw r and s values
- */
- private static byte[] getDEREncodedSignature (byte[] rawSignatureValue) {
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
-
- // First we separate x and y of coordinates into separate byte arrays r and s
- byte[] r = new byte[32];
- byte[] s = new byte[32];
-
- try {
- System.arraycopy(rawSignatureValue, 0, r, 0, 32);
- System.arraycopy(rawSignatureValue, 32, s, 0, 32);
- } catch (ArrayIndexOutOfBoundsException e) {
- getLogger().error("ArrayIndexOutOfBoundsException occurred while trying to get DER encoded signature", e);
- return new byte[0];
- }
-
- // Then encode both parts (r & s) individually
- byte[] rDerEncoded = getDerEncodedSignatureValue(r);
- byte[] sDerEncoded = getDerEncodedSignatureValue(s);
-
- // And write everything with the proper header to the buffer
- baos.write(0x30);
- baos.write(rDerEncoded.length + sDerEncoded.length);
- try {
- baos.write(rDerEncoded);
- baos.write(sDerEncoded);
- } catch (IOException e) {
- getLogger().error("IOException occurred while trying to write DER encoded signature r and s value", e);
- }
-
- byte[] derEncodedSignature = baos.toByteArray();
-
- try {
- baos.close();
- } catch (IOException e) {
- getLogger().error("IOException occurred while trying to close ByteArrayOutputStream", e);
- }
-
- if (showSignatureVerificationLog) {
- StringBuilder sb = new StringBuilder();
- sb.append("Signature encoding raw -> DER:").append(System.lineSeparator());
- sb.append("\tRaw: ").append(ByteUtils.toHexString(rawSignatureValue)).append(System.lineSeparator());
- sb.append("\tR: ").append(ByteUtils.toHexString(r)).append(System.lineSeparator());
- sb.append("\tR (DER-encoded): ").append(ByteUtils.toHexString(rDerEncoded)).append(System.lineSeparator());
- sb.append("\tS: ").append(ByteUtils.toHexString(s)).append(System.lineSeparator());
- sb.append("\tS (DER-encoded): ").append(ByteUtils.toHexString(sDerEncoded)).append(System.lineSeparator());
- sb.append("\tDER: ").append(ByteUtils.toHexString(derEncodedSignature));
- getLogger().debug(sb.toString());
- }
-
- return derEncodedSignature;
- }
-
-
- /**
- * Helper function which provides a partial DER encoding for positive integer values used for r and s
- *
- * @param value byte array containing a positive integer (non two's complement)
- * @return DER-encoded value of r or s (depending on the @param), including int content type, length and, if needed, padding
- */
- private static byte[] getDerEncodedSignatureValue(byte[] value) {
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- // Check if the value is negative which is equivalent to r[0] being bigger than 0x7f
- boolean isFillByteNeeded = value[0] < 0;
-
- int indexOfFirstNonNullValue = 0;
- for (/* empty init statement */; indexOfFirstNonNullValue < value.length; indexOfFirstNonNullValue++) {
- if (value[indexOfFirstNonNullValue] != 0) {
- break;
- }
- }
-
- byte derEncodedLength = (byte) (value.length - indexOfFirstNonNullValue);
-
- baos.write(0x02);
- if (isFillByteNeeded) {
- baos.write(derEncodedLength + 1);
- baos.write(0x00);
- } else {
- baos.write(derEncodedLength);
- }
-
- baos.write(value, indexOfFirstNonNullValue, value.length - indexOfFirstNonNullValue);
- byte[] result = baos.toByteArray();
-
- try {
- baos.close();
- } catch (IOException e) {
- getLogger().error("IOException occurred while trying to close ByteArrayOutputStream", e);
- }
-
- return result;
- }
-
-
- /**
- * Sets the SSLContext of the TLSServer and TLSClient with the given keystore and truststore locations as
- * well as the password protecting the keystores/truststores.
- *
- * @param keyStorePath The relative path and filename for the keystore
- * @param trustStorePath The relative path and filename for the truststore
- * @param keyStorePassword The password protecting the keystore
- */
- public static void setSSLContext(
- String keyStorePath,
- String trustStorePath,
- String keyStorePassword) {
- KeyStore keyStore = SecurityUtils.getKeyStore(keyStorePath, keyStorePassword);
- KeyStore trustStore = SecurityUtils.getKeyStore(trustStorePath, keyStorePassword);
- try {
- // Initialize a key manager factory with the keystore
- KeyManagerFactory keyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
- keyFactory.init(keyStore, keyStorePassword.toCharArray());
- KeyManager[] keyManagers = keyFactory.getKeyManagers();
- // Initialize a trust manager factory with the truststore
- TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
- trustFactory.init(trustStore);
- TrustManager[] trustManagers = trustFactory.getTrustManagers();
- // Initialize an SSL context to use these managers and set as default
- SSLContext sslContext = SSLContext.getInstance("TLS");
- sslContext.init(keyManagers, trustManagers, null);
- SSLContext.setDefault(sslContext);
- } catch (NoSuchAlgorithmException | UnrecoverableKeyException | KeyStoreException |
- KeyManagementException e) {
- getLogger().error(e.getClass().getSimpleName() + " occurred while trying to initialize SSL context");
- }
- }
-
- /**
- * Checks the syntax of the EMAID according to Annex H.1 of ISO 15118-2
- *
- * @param certChain The contract certificate chain. The EMAID is read from the contract certificate's common name
- * @return True, if the syntax is valid, false otherwise
- */
- public static boolean isEMAIDSyntaxValid(X509Certificate contractCertificate) {
- String emaid = getEMAID(contractCertificate).getValue().toUpperCase();
-
- if (emaid.length() < 14 || emaid.length() > 18) {
- getLogger().error("EMAID is invalid. Its length (" + emaid.length() + ") mus be between "
- + "14 (min, excluding separators) and 18 (max, including separators)");
- return false;
- }
-
- String emaidWithoutSeparator = emaid.replace("-", "");
-
- // Check country code
- if (Character.isDigit(emaidWithoutSeparator.charAt(0)) || Character.isDigit(emaidWithoutSeparator.charAt(1))) {
- getLogger().error("EMAID (" + emaid + ") is invalid, the first two characters must not be a digit");
- return false;
- }
-
- // Check provider ID
- if (! (Character.isLetterOrDigit(emaidWithoutSeparator.charAt(2)) &&
- Character.isLetterOrDigit(emaidWithoutSeparator.charAt(3)) &&
- Character.isLetterOrDigit(emaidWithoutSeparator.charAt(4))) ) {
- getLogger().error("EMAID (" + emaid + ") is invalid, the provider ID must be alpha-numerical");
- return false;
- }
-
- // Check emaInstance
- if (! (Character.isLetterOrDigit(emaidWithoutSeparator.charAt(5)) &&
- Character.isLetterOrDigit(emaidWithoutSeparator.charAt(6)) &&
- Character.isLetterOrDigit(emaidWithoutSeparator.charAt(7)) &&
- Character.isLetterOrDigit(emaidWithoutSeparator.charAt(8)) &&
- Character.isLetterOrDigit(emaidWithoutSeparator.charAt(9)) &&
- Character.isLetterOrDigit(emaidWithoutSeparator.charAt(10)) &&
- Character.isLetterOrDigit(emaidWithoutSeparator.charAt(11)) &&
- Character.isLetterOrDigit(emaidWithoutSeparator.charAt(12)) &&
- Character.isLetterOrDigit(emaidWithoutSeparator.charAt(13))) ) {
- getLogger().error("EMAID (" + emaid + ") is invalid, the eMA instance must be alpha-numerical");
- return false;
- }
-
- return true;
- }
-
- public static void setExiCodec(ExiCodec exiCodecChoice) {
- exiCodec = exiCodecChoice;
- }
-
- private static ExiCodec getExiCodec() {
- return exiCodec;
- }
- }
|