org.signserver.module.mrtdsodsigner.MRTDSODSigner.java Source code

Java tutorial

Introduction

Here is the source code for org.signserver.module.mrtdsodsigner.MRTDSODSigner.java

Source

/*************************************************************************
 *                                                                       *
 *  SignServer: The OpenSource Automated Signing Server                  *
 *                                                                       *
 *  This software is free software; you can redistribute it and/or       *
 *  modify it under the terms of the GNU Lesser General Public           *
 *  License as published by the Free Software Foundation; either         *
 *  version 2.1 of the License, or any later version.                    *
 *                                                                       *
 *  See terms of license at gnu.org.                                     *
 *                                                                       *
 *************************************************************************/
package org.signserver.module.mrtdsodsigner;

import java.io.*;
import java.security.*;
import java.security.cert.*;
import java.security.cert.Certificate;
import java.util.*;

import javax.persistence.EntityManager;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.cert.jcajce.JcaX500NameUtil;
import org.bouncycastle.jcajce.provider.digest.Tiger;
import org.ejbca.util.CertTools;
import org.signserver.common.*;
import org.signserver.module.mrtdsodsigner.jmrtd.SODFile;
import org.signserver.server.WorkerContext;
import org.signserver.server.archive.Archivable;
import org.signserver.server.archive.DefaultArchivable;
import org.signserver.server.cryptotokens.ICryptoInstance;
import org.signserver.server.cryptotokens.ICryptoToken;
import org.signserver.server.signers.BaseSigner;

import com.tigerit.mrtd.*;

/**
 * A Signer creating a signed Security Object Data (SOD) file to be stored in ePassports.
 * <p>
 * Properties:
 * <ul>
 * <li>DIGESTALGORITHM = Message digest algorithm that is applied or should be applied to the values. (Optional)</li>
 * <li>SIGNATUREALGORITHM = Signature algorithm for signing the SO(d), should match
 * the digest algorithm. (Optional)</li>
 * <li>DODATAGROUPHASHING = True if this signer first should hash to values. Otherwise
 * the values are assumed to be hashes</li>
 * <li>LDSVERSION = Version of Logical Data Structure (LDS). For LDS version 1.7 enter "0107" and for version 1.8 "0108". (Optional, default is 0107)</li>
 * <li>UNICODEVERSION = Version of Unicode used in the datagroups. Required if LDS 1.8 is used. Example: "040000" for Unicode version 4.0.0.</li>
 * </ul>
 *
 * @author Tomas Gustavsson
 * @author Markus Kils
 * @version $Id: MRTDSODSigner.java 5977 2015-03-27 10:30:50Z netmackan $
 */
public class MRTDSODSigner extends BaseSigner {

    private static final Logger log = Logger.getLogger(MRTDSODSigner.class);

    /**
     * The digest algorithm, for example SHA1, SHA256. Defaults to SHA256.
     */
    private static final String PROPERTY_DIGESTALGORITHM = "DIGESTALGORITHM";

    /**
     * Default value for the digestAlgorithm property
     */
    private static final String DEFAULT_DIGESTALGORITHM = "SHA256";

    /**
     * The signature algorithm, for example SHA1withRSA, SHA256withRSA, SHA256withECDSA. Defaults to SHA256withRSA.
     */
    private static final String PROPERTY_SIGNATUREALGORITHM = "SIGNATUREALGORITHM";

    /**
     * Default value for the signature algorithm property
     */
    private static final String DEFAULT_SIGNATUREALGORITHM = "SHA256withRSA";

    /**
     * Determines if the the data group values should be hashed by the signer. If false we assume they are already hashed.
     */
    private static final String PROPERTY_DODATAGROUPHASHING = "DODATAGROUPHASHING";

    /**
     * Default value if the data group values should be hashed by the signer.
     */
    private static final String DEFAULT_DODATAGROUPHASHING = "false";

    /**
     * Determines which version of the LDS to use.
     */
    private static final String PROPERTY_LDSVERSION = "LDSVERSION";

    /**
     * Default value if the LDS version is not specified.
     */
    private static final String DEFAULT_LDSVERSION = "0107";

    /**
     * Determines which version of Unicode to set.
     */
    private static final String PROPERTY_UNICODEVERSION = "UNICODEVERSION";

    private static final Object syncObj = new Object();

    private List<String> configErrors;

    @Override
    public void init(int workerId, WorkerConfig config, WorkerContext workerContext, EntityManager workerEM) {
        super.init(workerId, config, workerContext, workerEM);

        configErrors = new LinkedList<String>();

        if (hasSetIncludeCertificateLevels) {
            configErrors.add(WorkerConfig.PROPERTY_INCLUDE_CERTIFICATE_LEVELS + " is not supported.");
        }
    }

    @Override
    public ProcessResponse processData(ProcessRequest signRequest, RequestContext requestContext)
            throws IllegalRequestException, CryptoTokenOfflineException, SignServerException {
        if (log.isTraceEnabled()) {
            log.trace(">processData");
        }
        ProcessResponse ret = null;

        // Check that the request contains a valid SODSignRequest object.
        if (signRequest instanceof SODSignRequest) {
            //throw new IllegalRequestException("Recieved request wasn't an expected SODSignRequest.");

            final ISignRequest sReq = (ISignRequest) signRequest;

            final SODSignRequest sodRequest = (SODSignRequest) signRequest;

            final ICryptoToken token = getCryptoToken();
            // Trying to do a workaround for issue when the PKCS#11 session becomes invalid
            // If autoactivate is on, we can deactivate and re-activate the token.
            synchronized (syncObj) {
                int status = token.getCryptoTokenStatus();
                if (log.isDebugEnabled()) {
                    log.debug("Crypto token status: " + status);
                }
                if (status != WorkerStatus.STATUS_ACTIVE) {
                    log.info("Crypto token status is not active, will see if we can autoactivate.");
                    String pin = config.getProperty("PIN");
                    if (pin == null) {
                        pin = config.getProperty("pin");
                    }
                    if (pin != null) {
                        log.info("Deactivating and re-activating crypto token.");
                        token.deactivate();
                        try {
                            token.activate(pin);
                        } catch (CryptoTokenAuthenticationFailureException e) {
                            throw new CryptoTokenOfflineException(e);
                        }
                    } else {
                        log.info("Autoactivation not enabled, can not re-activate crypto token.");
                    }
                }
            }

            // Construct SOD
            final SODFile sod;
            final X509Certificate cert;
            final List<Certificate> certChain;
            ICryptoInstance crypto = null;
            try {
                crypto = acquireCryptoInstance(ICryptoToken.PURPOSE_SIGN, signRequest, requestContext);

                cert = (X509Certificate) getSigningCertificate(crypto);
                if (cert == null) {
                    throw new CryptoTokenOfflineException("No signing certificate");
                }
                if (log.isDebugEnabled()) {
                    log.debug("Using signer certificate with subjectDN '" + CertTools.getSubjectDN(cert)
                            + "', issuerDN '" + CertTools.getIssuerDN(cert) + ", serNo "
                            + CertTools.getSerialNumberAsString(cert));
                }
                certChain = getSigningCertificateChain(crypto);

                // Create the SODFile using the data group hashes that was sent to us in the request.
                final String digestAlgorithm = config.getProperty(PROPERTY_DIGESTALGORITHM,
                        DEFAULT_DIGESTALGORITHM);
                final String digestEncryptionAlgorithm = config.getProperty(PROPERTY_SIGNATUREALGORITHM,
                        DEFAULT_SIGNATUREALGORITHM);
                if (log.isDebugEnabled()) {
                    log.debug("Using algorithms " + digestAlgorithm + ", " + digestEncryptionAlgorithm);
                }
                final String doHashing = config.getProperty(PROPERTY_DODATAGROUPHASHING,
                        DEFAULT_DODATAGROUPHASHING);
                final Map<Integer, byte[]> dgvalues = sodRequest.getDataGroupHashes();
                Map<Integer, byte[]> dghashes = dgvalues;
                if (StringUtils.equalsIgnoreCase(doHashing, "true")) {
                    if (log.isDebugEnabled()) {
                        log.debug("Converting data group values to hashes using algorithm " + digestAlgorithm);
                    }
                    // If true here the "data group hashes" are not really hashes but values that we must hash.
                    // The input is already decoded (if needed) and nice, so we just need to hash it
                    dghashes = new HashMap<Integer, byte[]>(16);
                    for (Map.Entry<Integer, byte[]> dgId : dgvalues.entrySet()) {
                        final byte[] value = dgId.getValue();
                        if (log.isDebugEnabled()) {
                            log.debug("Hashing data group " + dgId + ", value is of length: " + value.length);
                        }
                        if ((value != null) && (value.length > 0)) {
                            MessageDigest digest = MessageDigest.getInstance(digestAlgorithm);
                            byte[] result = digest.digest(value);
                            if (log.isDebugEnabled()) {
                                log.debug("Resulting hash is of length: " + result.length);
                            }
                            dghashes.put(dgId.getKey(), result);
                        }
                    }
                }

                // Version values from configuration
                String ldsVersion = config.getProperty(PROPERTY_LDSVERSION, DEFAULT_LDSVERSION);
                String unicodeVersion = config.getProperty(PROPERTY_UNICODEVERSION);

                // Version values in request overrides configuration
                final String ldsVersionRequest = sodRequest.getLdsVersion();
                if (ldsVersionRequest != null) {
                    ldsVersion = ldsVersionRequest;
                }
                final String unicodeVersionRequest = sodRequest.getUnicodeVersion();
                if (unicodeVersionRequest != null) {
                    unicodeVersion = unicodeVersionRequest;
                }

                // Check version
                if ("0107".equals(ldsVersion)) {
                    // LDS V1.7 does not supported the version fields
                    ldsVersion = null;
                    unicodeVersion = null;
                } else if ("0108".equals(ldsVersion)) {
                    // LDS V1.8 requires a unicode version
                    if (unicodeVersion == null) {
                        throw new IllegalRequestException("Unicode version must be specified in LDS version 1.8");
                    }
                } else {
                    throw new IllegalRequestException("Unsupported LDS version: " + ldsVersion);
                }
                if (log.isDebugEnabled()) {
                    log.debug("LDS version: " + ldsVersion + ", unicodeVerison: " + unicodeVersion);
                }
                // TODO : Sun : This should be uncomment
                /*
                final SODFile constructedSod
                = new SODFile(digestAlgorithm, digestEncryptionAlgorithm,
                dghashes, crypto.getPrivateKey(), cert, crypto.getProvider().getName(),
                ldsVersion, unicodeVersion);
                */
                // TODO : Sun : This should be removed
                byte[] constructedSodEncodedData = getSodDataFromTigeritJars(dgvalues, crypto.getPrivateKey(),
                        cert);
                sod = new SODFile(new ByteArrayInputStream(constructedSodEncodedData));
                // Reconstruct the sod
                //sod = new SODFile(new ByteArrayInputStream(constructedSod.getEncoded()));

            } catch (NoSuchAlgorithmException ex) {
                throw new SignServerException("Problem constructing SOD", ex);
            } catch (TigerSignerException ex) {
                throw new SignServerException("Problem constructing SOD", ex);
            } catch (IOException ex) {
                throw new SignServerException("Problem reconstructing SOD", ex);
            } finally {
                releaseCryptoInstance(crypto, requestContext);
            }

            // Verify the Signature before returning
            try {
                verifySignatureAndChain(sod, certChain);

                if (log.isDebugEnabled()) {
                    log.debug("SOD verified correctly, returning SOD.");
                }
                // Return response
                final byte[] signedbytes = sod.getEncoded();
                final String archiveId = createArchiveId(signedbytes,
                        (String) requestContext.get(RequestContext.TRANSACTION_ID));
                final Collection<? extends Archivable> archivables = Arrays
                        .asList(new DefaultArchivable(Archivable.TYPE_RESPONSE, signedbytes, archiveId));
                ret = new SODSignResponse(sReq.getRequestID(), signedbytes, cert, archiveId, archivables);

                // The client can be charged for the request
                requestContext.setRequestFulfilledByWorker(true);
            } catch (TigerSignerException e) {
                log.error("Error verifying the SOD we signed ourselves. ", e);
                throw new SignServerException("SOD verification failure", e);
            } catch (IOException e) {
                log.error("Error encoding SOD", e);
                throw new SignServerException("SOD encoding failure", e);
            }

            if (log.isTraceEnabled()) {
                log.trace("<processData");
            }
        }

        // TODO : SUN : Check  : This is a added method for BulkSodSignResponse

        else if (signRequest instanceof BulkSodSignRequest) {
            final ISignRequest sReq = (ISignRequest) signRequest;

            final BulkSodSignRequest bulkSodSignRequest = (BulkSodSignRequest) signRequest;

            final ICryptoToken token = getCryptoToken();
            // Trying to do a workaround for issue when the PKCS#11 session becomes invalid
            // If autoactivate is on, we can deactivate and re-activate the token.
            synchronized (syncObj) {
                int status = token.getCryptoTokenStatus();
                if (log.isDebugEnabled()) {
                    log.debug("Crypto token status: " + status);
                }
                if (status != WorkerStatus.STATUS_ACTIVE) {
                    log.info("Crypto token status is not active, will see if we can autoactivate.");
                    String pin = config.getProperty("PIN");
                    if (pin == null) {
                        pin = config.getProperty("pin");
                    }
                    if (pin != null) {
                        log.info("Deactivating and re-activating crypto token.");
                        token.deactivate();
                        try {
                            token.activate(pin);
                        } catch (CryptoTokenAuthenticationFailureException e) {
                            throw new CryptoTokenOfflineException(e);
                        }
                    } else {
                        log.info("Autoactivation not enabled, can not re-activate crypto token.");
                    }
                }
            }

            // Construct SOD
            final X509Certificate cert;
            final List<Certificate> certChain;
            Map<String, SODFile> bulkSodData = new HashMap<String, SODFile>();
            List<NotProcessedSodData> notProcessedSodData = new ArrayList<NotProcessedSodData>();

            ICryptoInstance crypto = null;
            try {
                crypto = acquireCryptoInstance(ICryptoToken.PURPOSE_SIGN, signRequest, requestContext);

                cert = (X509Certificate) getSigningCertificate(crypto);
                if (cert == null) {
                    throw new CryptoTokenOfflineException("No signing certificate");
                }
                if (log.isDebugEnabled()) {
                    log.debug("Using signer certificate with subjectDN '" + CertTools.getSubjectDN(cert)
                            + "', issuerDN '" + CertTools.getIssuerDN(cert) + ", serNo "
                            + CertTools.getSerialNumberAsString(cert));
                }
                certChain = getSigningCertificateChain(crypto);

                // Create the SODFile using the data group hashes that was sent to us in the request.
                final String digestAlgorithm = config.getProperty(PROPERTY_DIGESTALGORITHM,
                        DEFAULT_DIGESTALGORITHM);
                final String digestEncryptionAlgorithm = config.getProperty(PROPERTY_SIGNATUREALGORITHM,
                        DEFAULT_SIGNATUREALGORITHM);
                if (log.isDebugEnabled()) {
                    log.debug("Using algorithms " + digestAlgorithm + ", " + digestEncryptionAlgorithm);
                }
                final String doHashing = config.getProperty(PROPERTY_DODATAGROUPHASHING,
                        DEFAULT_DODATAGROUPHASHING);
                final Map<String, Map<Integer, byte[]>> bulkDataGroups = bulkSodSignRequest.getBulkDataGroups();

                // Version values from configuration
                String ldsVersion = config.getProperty(PROPERTY_LDSVERSION, DEFAULT_LDSVERSION);
                String unicodeVersion = config.getProperty(PROPERTY_UNICODEVERSION);

                // Version values in request overrides configuration
                final String ldsVersionRequest = bulkSodSignRequest.getLdsVersion();
                if (ldsVersionRequest != null) {
                    ldsVersion = ldsVersionRequest;
                }
                final String unicodeVersionRequest = bulkSodSignRequest.getUnicodeVersion();
                if (unicodeVersionRequest != null) {
                    unicodeVersion = unicodeVersionRequest;
                }

                // Check version
                if ("0107".equals(ldsVersion)) {
                    // LDS V1.7 does not supported the version fields
                    ldsVersion = null;
                    unicodeVersion = null;
                } else if ("0108".equals(ldsVersion)) {
                    // LDS V1.8 requires a unicode version
                    if (unicodeVersion == null) {
                        throw new IllegalRequestException("Unicode version must be specified in LDS version 1.8");
                    }
                } else {
                    throw new IllegalRequestException("Unsupported LDS version: " + ldsVersion);
                }
                if (log.isDebugEnabled()) {
                    log.debug("LDS version: " + ldsVersion + ", unicodeVerison: " + unicodeVersion);
                }
                // TODO : Sun : This should be uncomment
                /*
                final SODFile constructedSod
                = new SODFile(digestAlgorithm, digestEncryptionAlgorithm,
                dghashes, crypto.getPrivateKey(), cert, crypto.getProvider().getName(),
                ldsVersion, unicodeVersion);
                */
                // TODO : Sun : This should be removed
                for (Map.Entry<String, Map<Integer, byte[]>> entry : bulkDataGroups.entrySet()) {
                    SODFile sod = null;
                    NotProcessedSodData notProcessedData = new NotProcessedSodData(entry.getKey());

                    try {
                        byte[] constructedSodEncodedData = getSodDataFromTigeritJars(entry.getValue(),
                                crypto.getPrivateKey(), cert);
                        sod = new SODFile(new ByteArrayInputStream(constructedSodEncodedData));
                        bulkSodData.put(entry.getKey(), sod);
                    } catch (TigerSignerException e) {
                        notProcessedData.setExceptionMessage(e.getMessage());
                        notProcessedSodData.add(notProcessedData);
                    } catch (IOException e) {
                        notProcessedData.setExceptionMessage(e.getMessage());
                        notProcessedSodData.add(notProcessedData);
                    }
                }
            } finally {
                releaseCryptoInstance(crypto, requestContext);
            }

            // Verify the Signature before returning

            List<SODData> bulkSignedBytes = new ArrayList<SODData>();

            for (Map.Entry<String, SODFile> sodData : bulkSodData.entrySet()) {
                try {
                    verifySignatureAndChain(sodData.getValue(), certChain);

                    if (log.isDebugEnabled()) {
                        log.debug("SOD verified correctly, returning SOD.");
                    }

                    final byte[] signedbytes = sodData.getValue().getEncoded();

                    SODData data = new SODData(sodData.getKey(), signedbytes);
                    bulkSignedBytes.add(data);
                } catch (Exception e) {
                    NotProcessedSodData notProcessedData = new NotProcessedSodData(sodData.getKey(),
                            e.getMessage());
                    notProcessedSodData.add(notProcessedData);
                }
            }
            ret = new BulkSodSignResponse(sReq.getRequestID(), new ProcessedSODDataList(bulkSignedBytes),
                    new NotProcessedSodDataList(notProcessedSodData), cert);

            // The client can be charged for the request
            requestContext.setRequestFulfilledByWorker(true);

        } else {
            throw new IllegalRequestException("Recieved request wasn't an expected SODSignRequest.");
        }
        return ret;
    }

    private byte[] getSodDataFromTigeritJars(Map<Integer, byte[]> dgValues, PrivateKey privateKey,
            X509Certificate certificate) throws TigerSignerException {
        byte[] sodData = null;
        IsoDataProcessor mdp = new IsoDataProcessor();
        Response response = new Response();
        DgContents dgContents = getLDSData(dgValues);
        byte[] dataToBeSigned = mdp.GetDataForSigning(dgContents, response);
        try {
            if (response.IsSuccessful) {
                byte[] signedData = getSampleSignedData(dataToBeSigned, privateKey);
                SodSecurityInfo ldsSecurityInfo = new SodSecurityInfo();

                ldsSecurityInfo.Certificate = certificate;
                ldsSecurityInfo.SignatureData = signedData;

                sodData = mdp.GetSod(ldsSecurityInfo, response);
                if (response.IsSuccessful) {
                    log.info("SOD generated Successfully");
                }
            }
        } catch (NoSuchAlgorithmException e) {
            throw new TigerSignerException("Error while constracting SOD. ", e);
        } catch (SignatureException e) {
            throw new TigerSignerException("Error while constracting SOD. ", e);
        } catch (InvalidKeyException e) {
            throw new TigerSignerException("Error while constracting SOD. ", e);
        }
        if (sodData == null) {
            throw new TigerSignerException("Error while constracting SOD : Null SOD data.");
        }
        return sodData;
    }

    static byte[] getSampleSignedData(byte[] dataToBeSigned, PrivateKey privateKey)
            throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
        byte[] encryptedDigest = null;

        Signature s = null;
        s = Signature.getInstance("SHA256withRSA");
        s.initSign(privateKey);
        s.update(dataToBeSigned);
        encryptedDigest = s.sign();
        return encryptedDigest;
    }

    private DgContents getLDSData(Map<Integer, byte[]> dgValues) {
        DgContents ldsData = new DgContents();
        //Dg1, Dg2, Dg4, Dg5, Dg7, Dg11;
        ldsData.Dg1 = dgValues.get(1);
        ldsData.Dg2 = dgValues.get(2);
        ldsData.Dg4 = dgValues.get(4);
        ldsData.Dg5 = dgValues.get(5);
        ldsData.Dg7 = dgValues.get(7);
        ldsData.Dg11 = dgValues.get(11);
        return ldsData;
    }

    private X509Certificate findIssuerCert(Collection<Certificate> chain, X509Certificate sodCert) {
        X509Certificate result = null;
        final X500Name issuer = JcaX500NameUtil.getIssuer(sodCert);
        if (log.isDebugEnabled()) {
            final StringBuilder buff = new StringBuilder();
            buff.append("Looking for ");
            buff.append(issuer);
            log.debug(buff.toString());
        }
        for (Certificate cert : chain) {
            if (cert instanceof X509Certificate) {
                final X509Certificate x509 = (X509Certificate) cert;
                final X500Name subject = JcaX500NameUtil.getSubject(x509);
                if (issuer.equals(subject)) {
                    result = (X509Certificate) cert;
                    if (log.isDebugEnabled()) {
                        log.debug("Found issuer");
                    }
                    break;
                } else {
                    if (log.isDebugEnabled()) {
                        final StringBuilder buff = new StringBuilder();
                        buff.append(issuer);
                        buff.append("!=");
                        buff.append(subject);
                        log.debug(buff.toString());
                    }
                }
            }
        }
        return result;
    }

    private void verifySignatureAndChain(final SODFile sod, final Collection<Certificate> chain)
            throws TigerSignerException {
        try {
            if (log.isDebugEnabled()) {
                final StringBuilder buff = new StringBuilder();
                buff.append("Verifying SOD signed by DS with issuer: ");
                buff.append(sod.toString());
                log.debug(buff.toString());
            }

            // Get Signer certificate from SOD
            final X509Certificate sodCert = sod.getDocSigningCertificate();

            // We need a Bouncy Castle certificate so reconstruct it
            final CertificateFactory factory = CertificateFactory.getInstance("X.509", "BC");
            final X509Certificate signerCert = (X509Certificate) factory
                    .generateCertificate(new ByteArrayInputStream(sodCert.getEncoded()));

            // Verify the SOD signature using certificate from SOD
            final boolean consistent = sod.checkDocSignature(signerCert);
            if (!consistent) {
                log.error("Failed to verify the SOD we signed ourselves.");
                log.error("Cert: " + signerCert);
                log.error("SOD: " + sod);
                throw new TigerSignerException("GeneralSecurityException : Signature not consistent");
            }

            // Find the issuer certificate from the configured chain
            final X509Certificate issuerCert = (chain == null ? null : findIssuerCert(chain, signerCert));
            if (issuerCert == null) {
                log.error("Failed to verify certificate chain");
                log.error("Cert: " + signerCert);
                log.error("SOD Cert: " + signerCert);
                log.error("Chain: " + chain);
                throw new TigerSignerException("GeneralSecurityException :Issuer of cert not in chain.");
            }

            // Verify the signer certificate using the issuer from the chain
            signerCert.verify(issuerCert.getPublicKey());
        } catch (IOException e) {
            log.error("Getting signer certificate from SOD failed", e);
            throw new TigerSignerException("GeneralSecurityException : Getting signer certificate from SOD failed",
                    e);
        } catch (CertificateEncodingException e) {
            throw new TigerSignerException("CertificateEncodingException : ", e);
        } catch (CertificateException e) {
            throw new TigerSignerException("CertificateException : ", e);
        } catch (NoSuchAlgorithmException e) {
            throw new TigerSignerException("NoSuchAlgorithmException : ", e);
        } catch (InvalidKeyException e) {
            throw new TigerSignerException("InvalidKeyException : ", e);
        } catch (SignatureException e) {
            throw new TigerSignerException("SignatureException : ", e);
        } catch (NoSuchProviderException e) {
            throw new TigerSignerException("NoSuchProviderException : ", e);
        } catch (GeneralSecurityException e) {
            throw new TigerSignerException("GeneralSecurityException : ", e);
        }
    }

    @Override
    protected List<String> getFatalErrors() {
        final List<String> errors = super.getFatalErrors();

        errors.addAll(configErrors);
        return errors;
    }
}