eu.europa.esig.dss.DSSUtils.java Source code

Java tutorial

Introduction

Here is the source code for eu.europa.esig.dss.DSSUtils.java

Source

/**
 * DSS - Digital Signature Services
 * Copyright (C) 2015 European Commission, provided under the CEF programme
 *
 * This file is part of the "DSS - Digital Signature Services" project.
 *
 * This library 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 (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
package eu.europa.esig.dss;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Provider;
import java.security.Security;
import java.security.cert.CRLException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;

import javax.security.auth.x500.X500Principal;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.xml.security.exceptions.XMLSecurityException;
import org.apache.xml.security.keys.content.x509.XMLX509SKI;
import org.bouncycastle.cert.X509CRLHolder;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CRLConverter;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import eu.europa.esig.dss.client.http.DataLoader;
import eu.europa.esig.dss.x509.CertificateToken;
import eu.europa.esig.dss.x509.Token;

public final class DSSUtils {

    private static final Logger logger = LoggerFactory.getLogger(DSSUtils.class);

    public static final String CERT_BEGIN = "-----BEGIN CERTIFICATE-----\n";
    public static final String CERT_END = "-----END CERTIFICATE-----";

    private static final BouncyCastleProvider securityProvider = new BouncyCastleProvider();

    private static final CertificateFactory certificateFactory;

    public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];

    public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";

    /**
     * The default date pattern: "yyyy-MM-dd"
     */
    public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";

    static {
        try {
            Security.addProvider(securityProvider);
            certificateFactory = CertificateFactory.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME);
        } catch (CertificateException e) {
            logger.error(e.getMessage(), e);
            throw new DSSException("Platform does not support X509 certificate", e);
        } catch (NoSuchProviderException e) {
            logger.error(e.getMessage(), e);
            throw new DSSException("Platform does not support BouncyCastle", e);
        }
    }

    /**
     * This class is an utility class and cannot be instantiated.
     */
    private DSSUtils() {
    }

    /**
     * Formats a date to use for internal purposes (logging, toString)
     *
     * @param date
     *            the date to be converted
     * @return the textual representation (a null date will result in "N/A")
     */
    public static String formatInternal(final Date date) {
        final String formatedDate = (date == null) ? "N/A"
                : new SimpleDateFormat(DEFAULT_DATE_TIME_FORMAT).format(date);
        return formatedDate;
    }

    /**
     * Formats the given date-time using the default pattern: {@code DSSUtils.DEFAULT_DATE_TIME_FORMAT}
     *
     * @param date
     * @return
     */
    public static String formatDate(final Date date) {
        if (date != null) {
            final String stringDate = new SimpleDateFormat(DSSUtils.DEFAULT_DATE_TIME_FORMAT).format(date);
            return stringDate;
        }
        return StringUtils.EMPTY;
    }

    /**
     * Converts the given string representation of the date using the {@code DEFAULT_DATE_TIME_FORMAT}.
     *
     * @param dateString
     *            the date string representation
     * @return the {@code Date}
     * @throws DSSException
     *             if the conversion is not possible the {@code DSSException} is thrown.
     */
    public static Date parseDate(final String dateString) throws DSSException {
        try {
            final SimpleDateFormat sdf = new SimpleDateFormat(DEFAULT_DATE_TIME_FORMAT);
            final Date date = sdf.parse(dateString);
            return date;
        } catch (ParseException e) {
            throw new DSSException(e);
        }
    }

    /**
     * Converts the given string representation of the date using the format pattern.
     *
     * @param format
     *            the format to use
     * @param dateString
     *            the date string representation
     * @return the {@code Date}
     * @throws DSSException
     *             if the conversion is not possible the {@code DSSException} is thrown.
     */
    public static Date parseDate(final String format, final String dateString) throws DSSException {
        try {
            final SimpleDateFormat sdf = new SimpleDateFormat(format);
            final Date date = sdf.parse(dateString);
            return date;
        } catch (ParseException e) {
            throw new DSSException(e);
        }
    }

    /**
     * Converts the given string representation of the date using the {@code DEFAULT_DATE_TIME_FORMAT}. If an exception is frown durring the prsing then null is returned.
     *
     * @param dateString
     *            the date string representation
     * @return the {@code Date} or null if the parsing is not possible
     */
    public static Date quietlyParseDate(final String dateString) throws DSSException {
        try {
            final SimpleDateFormat sdf = new SimpleDateFormat(DEFAULT_DATE_TIME_FORMAT);
            final Date date = sdf.parse(dateString);
            return date;
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * Converts an array of bytes into a String representing the hexadecimal values of each byte in order. The returned
     * String will be double the length of the passed array, as it takes two characters to represent any given byte. If
     * the input array is null then null is returned. The obtained string is converted to uppercase.
     *
     * @param value
     * @return
     */
    public static String toHex(final byte[] value) {
        return (value != null) ? new String(Hex.encodeHex(value, false)) : null;
    }

    /**
     * Converts a hexadecimal character to an integer.
     *
     * @param ch
     *            A character to convert to an integer digit
     * @param index
     *            The index of the character in the source
     * @return An integer
     * @throws DSSException
     *             Thrown if ch is an illegal hex character
     */
    protected static int toDigit(char ch, int index) throws DSSException {
        int digit = Character.digit(ch, 16);
        if (digit == -1) {
            throw new DSSException("Illegal hexadecimal character " + ch + " at index " + index);
        }
        return digit;
    }

    /**
     * This method re-encode base 64 encoded string to base 64 encoded byte array.
     *
     * @param base64String
     * @return
     */
    public static byte[] base64StringToBase64Binary(final String base64String) {
        final byte[] decodedBase64 = Base64.decodeBase64(base64String);
        final byte[] encodeBase64 = Base64.encodeBase64(decodedBase64);
        return encodeBase64;
    }

    /**
     * Writes bytes from a {@code byte[]} to an {@code OutputStream}.
     *
     * @param data
     *            the byte array to write, do not modify during output,
     *            null ignored
     * @param output
     *            the {@code OutputStream} to write to
     * @throws DSSException
     *             if output is null or an I/O error occurs
     * @since Commons IO 1.1
     */
    public static void write(byte[] data, OutputStream output) throws DSSException {
        try {
            if (data != null) {
                output.write(data);
            }
        } catch (IOException e) {
            throw new DSSException(e);
        }
    }

    /**
     * This method replaces all \ to /.
     *
     * @param path
     * @return
     */
    private static String normalisePath(String path) {
        return path.replace('\\', '/');
    }

    /**
     * This method checks if the resource with the given path exists.
     *
     * @param path
     * @return
     */
    public static boolean resourceExists(final String path) {
        final String path_ = normalisePath(path);
        final URL url = DSSUtils.class.getResource(path_);
        return url != null;
    }

    /**
     * This method checks if the file with the given path exists.
     *
     * @param path
     * @return
     */
    public static boolean fileExists(final String path) {
        final String path_ = normalisePath(path);
        final boolean exists = new File(path_).exists();
        return exists;
    }

    /**
     * This method returns a file reference. The file path is normalised (OS independent)
     *
     * @param filePath
     *            The path to the file.
     * @return
     */
    public static File getFile(final String filePath) {
        final String normalisedFolderFileName = normalisePath(filePath);
        final File file = new File(normalisedFolderFileName);
        return file;
    }

    /**
     * This method converts the given certificate into its PEM string.
     *
     * @param cert
     * @return
     * @throws java.security.cert.CertificateEncodingException
     */
    public static String convertToPEM(final CertificateToken cert) throws DSSException {
        final Base64 encoder = new Base64(64);
        final byte[] derCert = cert.getEncoded();
        final String pemCertPre = new String(encoder.encode(derCert));
        final String pemCert = CERT_BEGIN + pemCertPre + CERT_END;
        return pemCert;
    }

    /**
     * This method loads a certificate from the given resource. The certificate must be DER-encoded and may be supplied in binary or printable
     * (Base64) encoding. If the certificate is provided in Base64 encoding, it must be bounded at the beginning by -----BEGIN CERTIFICATE-----, and
     * must be bounded at the end by -----END CERTIFICATE-----. It throws an {@code DSSException} or return {@code null} when the
     * certificate cannot be loaded.
     *
     * @param path
     *            resource location.
     * @return
     */
    public static CertificateToken loadCertificate(final String path) throws DSSException {
        final InputStream inputStream = DSSUtils.class.getResourceAsStream(path);
        return loadCertificate(inputStream);
    }

    /**
     * This method loads a certificate from the given location. The certificate must be DER-encoded and may be supplied in binary or printable
     * (Base64) encoding. If the certificate is provided in Base64 encoding, it must be bounded at the beginning by -----BEGIN CERTIFICATE-----, and
     * must be bounded at the end by -----END CERTIFICATE-----. It throws an {@code DSSException} or return {@code null} when the
     * certificate cannot be loaded.
     *
     * @param file
     * @return
     */
    public static CertificateToken loadCertificate(final File file) throws DSSException {
        final InputStream inputStream = DSSUtils.toByteArrayInputStream(file);
        final CertificateToken x509Certificate = loadCertificate(inputStream);
        return x509Certificate;
    }

    /**
     * This method loads a certificate from the given location. The certificate must be DER-encoded and may be supplied in binary or printable (Base64) encoding. If the
     * certificate is provided in Base64 encoding, it must be bounded at the beginning by -----BEGIN CERTIFICATE-----, and must be bounded at the end by -----END CERTIFICATE-----.
     * It throws an {@code DSSException} or return {@code null} when the certificate cannot be loaded.
     *
     * @param inputStream
     *            input stream containing the certificate
     * @return
     */
    public static CertificateToken loadCertificate(final InputStream inputStream) throws DSSException {
        try {
            final X509Certificate cert = (X509Certificate) certificateFactory.generateCertificate(inputStream);
            return new CertificateToken(cert);
        } catch (CertificateException e) {
            throw new DSSException(e);
        }
    }

    /**
     * This method loads a certificate from the byte array. The certificate must be DER-encoded and may be supplied in binary or printable
     * (Base64) encoding. If the certificate is provided in Base64 encoding, it must be bounded at the beginning by -----BEGIN CERTIFICATE-----, and
     * must be bounded at the end by -----END CERTIFICATE-----. It throws an {@code DSSException} or return {@code null} when the
     * certificate cannot be loaded.
     *
     * @param input
     *            array of bytes containing the certificate
     * @return
     */
    public static CertificateToken loadCertificate(final byte[] input) throws DSSException {
        if (input == null) {
            throw new NullPointerException("X509 certificate");
        }
        final ByteArrayInputStream inputStream = new ByteArrayInputStream(input);
        return loadCertificate(inputStream);
    }

    /**
     * This method loads a certificate from a base 64 encoded String
     *
     * @param base64Encoded
     * @return
     */
    public static CertificateToken loadCertificateFromBase64EncodedString(final String base64Encoded) {
        final byte[] bytes = Base64.decodeBase64(base64Encoded);
        return loadCertificate(bytes);
    }

    /**
     * This method loads the issuer certificate from the given location (AIA). The certificate must be DER-encoded and may be supplied in binary or
     * printable (Base64) encoding. If the certificate is provided in Base64 encoding, it must be bounded at the beginning by -----BEGIN
     * CERTIFICATE-----, and must be bounded at the end by -----END CERTIFICATE-----. It throws an {@code DSSException} or return {@code null} when the certificate cannot be loaded.
     *
     * @param cert
     *            certificate for which the issuer should be loaded
     * @param loader
     *            the loader to use
     * @return
     */
    public static CertificateToken loadIssuerCertificate(final CertificateToken cert, final DataLoader loader) {
        List<String> urls = DSSASN1Utils.getAccessLocations(cert);
        if (CollectionUtils.isEmpty(urls)) {
            logger.info("There is no AIA extension for certificate download.");
            return null;
        }

        if (loader == null) {
            logger.warn(
                    "There is no DataLoader defined to load Certificates from AIA extension (urls : " + urls + ")");
            return null;
        }

        for (String url : urls) {
            logger.debug("Loading certificate from {}", url);

            byte[] bytes = loader.get(url);
            if (ArrayUtils.isNotEmpty(bytes)) {
                try {
                    logger.debug("Certificate : " + Base64.encodeBase64String(bytes));

                    CertificateToken issuerCert = loadCertificate(bytes);
                    if (issuerCert != null) {
                        if (!cert.getIssuerX500Principal().equals(issuerCert.getSubjectX500Principal())) {
                            logger.info(
                                    "There is AIA extension, but the issuer subject name and subject name does not match.");
                            logger.info("CERT ISSUER    : " + cert.getIssuerX500Principal().toString());
                            logger.info("ISSUER SUBJECT : " + issuerCert.getSubjectX500Principal().toString());
                        }
                        return issuerCert;
                    }
                } catch (Exception e) {
                    logger.warn("Unable to parse certficate from AIA (url:" + url + ") : " + e.getMessage());
                }
            } else {
                logger.error("Unable to read data from {}.", url);
            }
        }

        return null;
    }

    /**
     * This method return SKI bytes from certificate or null.
     *
     * @param x509Certificate
     *            {@code X509Certificate}
     * @return ski bytes from the given certificate
     * @throws Exception
     */
    public static byte[] getSki(final CertificateToken certificateToken) throws DSSException {
        try {
            final byte[] skiBytesFromCert = XMLX509SKI.getSKIBytesFromCert(certificateToken.getCertificate());
            return skiBytesFromCert;
        } catch (XMLSecurityException e) {
            return null;
        } catch (Exception e) {
            throw new DSSException(e);
        }
    }

    /**
     * This method loads a CRL from the given base 64 encoded string.
     *
     * @param base64Encoded
     * @return
     */
    public static X509CRL loadCRLBase64Encoded(final String base64Encoded) {
        final byte[] derEncoded = Base64.decodeBase64(base64Encoded);
        final X509CRL crl = loadCRL(new ByteArrayInputStream(derEncoded));
        return crl;
    }

    /**
     * This method loads a CRL from the given location.
     *
     * @param byteArray
     * @return
     */
    public static X509CRL loadCRL(final byte[] byteArray) {
        final ByteArrayInputStream inputStream = new ByteArrayInputStream(byteArray);
        final X509CRL crl = loadCRL(inputStream);
        return crl;
    }

    /**
     * This method loads a CRL from the given location.
     *
     * @param inputStream
     * @return
     */
    public static X509CRL loadCRL(final InputStream inputStream) {
        try {
            final X509CRL crl = (X509CRL) certificateFactory.generateCRL(inputStream);
            return crl;
        } catch (CRLException e) {
            throw new DSSException(e);
        }
    }

    /**
     * This method digests the given string with SHA1 algorithm and encode returned array of bytes as hex string.
     *
     * @param stringToDigest
     *            Everything in the name
     * @return hex encoded digest value
     */
    public static String getSHA1Digest(final String stringToDigest) {
        final byte[] digest = getMessageDigest(DigestAlgorithm.SHA1).digest(stringToDigest.getBytes());
        return Hex.encodeHexString(digest);
    }

    /**
     * This method digests the given {@code InputStream} with SHA1 algorithm and encode returned array of bytes as hex string.
     *
     * @param inputStream
     * @return
     */
    public static String getSHA1Digest(final InputStream inputStream) throws IOException {
        final byte[] bytes = IOUtils.toByteArray(inputStream);
        final byte[] digest = getMessageDigest(DigestAlgorithm.SHA1).digest(bytes);
        return Hex.encodeHexString(digest);
    }

    /**
     * This method replaces in a string one pattern by another one without using regexp.
     *
     * @param string
     * @param oldPattern
     * @param newPattern
     * @return
     */
    public static StringBuffer replaceStrStr(final StringBuffer string, final String oldPattern,
            final String newPattern) {
        if ((string == null) || (oldPattern == null) || oldPattern.equals("") || (newPattern == null)) {
            return string;
        }

        final StringBuffer replaced = new StringBuffer();
        int startIdx = 0;
        int idxOld;
        while ((idxOld = string.indexOf(oldPattern, startIdx)) >= 0) {
            replaced.append(string.substring(startIdx, idxOld));
            replaced.append(newPattern);
            startIdx = idxOld + oldPattern.length();
        }
        replaced.append(string.substring(startIdx));
        return replaced;
    }

    public static String replaceStrStr(final String string, final String oldPattern, final String newPattern) {
        final StringBuffer stringBuffer = replaceStrStr(new StringBuffer(string), oldPattern, newPattern);
        return stringBuffer.toString();
    }

    /**
     * This method allows to digest a token
     */
    public static String digest(DigestAlgorithm digestAlgoritm, Token token) {
        byte[] digest = digest(digestAlgoritm, token.getEncoded());
        return Base64.encodeBase64String(digest);
    }

    /**
     * This method allows to digest the data with the given algorithm.
     *
     * @param digestAlgorithm
     *            the algorithm to use
     * @param data
     *            the data to digest
     * @return digested array of bytes
     */
    public static byte[] digest(final DigestAlgorithm digestAlgorithm, final byte[] data) throws DSSException {
        final MessageDigest messageDigest = getMessageDigest(digestAlgorithm);
        final byte[] digestValue = messageDigest.digest(data);
        return digestValue;
    }

    /**
     * @param digestAlgorithm
     * @return
     * @throws NoSuchAlgorithmException
     */
    public static MessageDigest getMessageDigest(final DigestAlgorithm digestAlgorithm) {
        try {
            final String digestAlgorithmOid = digestAlgorithm.getOid();
            final MessageDigest messageDigest = MessageDigest.getInstance(digestAlgorithmOid);
            return messageDigest;
        } catch (NoSuchAlgorithmException e) {
            throw new DSSException("Digest algorithm '" + digestAlgorithm.getName() + "' error: " + e.getMessage(),
                    e);
        }
    }

    /**
     * This method allows to digest the data in the {@code InputStream} with the given algorithm.
     *
     * @param digestAlgo
     *            the algorithm to use
     * @param inputStream
     *            the data to digest
     * @return digested array of bytes
     */
    public static byte[] digest(final DigestAlgorithm digestAlgo, final InputStream inputStream)
            throws DSSException {
        try {

            final MessageDigest messageDigest = getMessageDigest(digestAlgo);
            final byte[] buffer = new byte[4096];
            int count = 0;
            while ((count = inputStream.read(buffer)) > 0) {
                messageDigest.update(buffer, 0, count);
            }
            final byte[] digestValue = messageDigest.digest();
            return digestValue;
        } catch (IOException e) {
            throw new DSSException(e);
        }
    }

    public static byte[] digest(DigestAlgorithm digestAlgorithm, byte[]... data) {
        final MessageDigest messageDigest = getMessageDigest(digestAlgorithm);
        for (final byte[] bytes : data) {

            messageDigest.update(bytes);
        }
        final byte[] digestValue = messageDigest.digest();
        return digestValue;
    }

    /**
     * Returns a {@code X509CertificateHolder} encapsulating the given {@code X509Certificate}.
     *
     * @param x509Certificate
     * @return a X509CertificateHolder holding this certificate
     */
    public static X509CertificateHolder getX509CertificateHolder(final CertificateToken certToken) {
        try {
            return new X509CertificateHolder(certToken.getEncoded());
        } catch (Exception e) {
            throw new DSSException(e);
        }
    }

    /**
     * This method opens the {@code URLConnection} using the given URL.
     *
     * @param url
     *            URL to be accessed
     * @return {@code URLConnection}
     */
    public static URLConnection openURLConnection(final String url) {
        try {
            final URL tspUrl = new URL(url);
            return tspUrl.openConnection();
        } catch (IOException e) {
            throw new DSSException(e);
        }
    }

    public static void writeToURLConnection(final URLConnection urlConnection, final byte[] bytes)
            throws DSSException {
        try {
            final OutputStream out = urlConnection.getOutputStream();
            out.write(bytes);
            out.close();
        } catch (IOException e) {
            throw new DSSException(e);
        }
    }

    /**
     * This method returns an {@code InputStream} which needs to be closed, based on {@code FileInputStream}.
     *
     * @param filePath
     *            The path to the file to read
     * @return an {@code InputStream} materialized by a {@code FileInputStream} representing the contents of the file
     * @throws DSSException
     */
    public static InputStream toInputStream(final String filePath) throws DSSException {
        final File file = getFile(filePath);
        final InputStream inputStream = toInputStream(file);
        return inputStream;
    }

    /**
     * This method returns an {@code InputStream} which needs to be closed, based on {@code FileInputStream}.
     *
     * @param file
     *            {@code File} to read.
     * @return an {@code InputStream} materialized by a {@code FileInputStream} representing the contents of the file
     * @throws DSSException
     */
    public static InputStream toInputStream(final File file) throws DSSException {
        if (file == null) {
            throw new NullPointerException();
        }
        try {
            final FileInputStream fileInputStream = openInputStream(file);
            return fileInputStream;
        } catch (IOException e) {
            throw new DSSException(e);
        }
    }

    /**
     * This method returns the {@code InputStream} based on the given {@code String} and char set. This stream does not need to be closed, it is based on {@code ByteArrayInputStream}.
     *
     * @param string
     *            {@code String} to convert
     * @param charset
     *            char set to use
     * @return the {@code InputStream} based on {@code ByteArrayInputStream}
     */
    public static InputStream toInputStream(final String string, final String charset) throws DSSException {
        try {
            final InputStream inputStream = new ByteArrayInputStream(string.getBytes(charset));
            return inputStream;
        } catch (UnsupportedEncodingException e) {
            throw new DSSException(e);
        }
    }

    /**
     * This method returns a {@code FileOutputStream} based on the provided path to the file.
     *
     * @param path
     *            to the file
     * @return {@code FileOutputStream}
     */
    public static FileOutputStream toFileOutputStream(final String path) throws DSSException {
        try {
            return new FileOutputStream(path);
        } catch (FileNotFoundException e) {
            throw new DSSException(e);
        }
    }

    /**
     * This method returns an {@code InputStream} which does not need to be closed, based on {@code ByteArrayInputStream}.
     *
     * @param file
     *            {@code File} to read
     * @return {@code InputStream} based on {@code ByteArrayInputStream}
     */
    public static InputStream toByteArrayInputStream(final File file) {
        if (file == null) {
            throw new NullPointerException();
        }
        try {
            final byte[] bytes = readFileToByteArray(file);
            final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
            return byteArrayInputStream;
        } catch (IOException e) {
            throw new DSSException(e);
        }
    }

    /**
     * This method returns the byte array representing the contents of the file.
     *
     * @param file
     *            {@code File} to read
     * @return an array of {@code byte}
     * @throws DSSException
     */
    public static byte[] toByteArray(final File file) throws DSSException {
        if (file == null) {
            throw new NullPointerException();
        }
        try {
            final byte[] bytes = readFileToByteArray(file);
            return bytes;
        } catch (IOException e) {
            throw new DSSException(e);
        }
    }

    /**
     * FROM: Apache
     * Reads the contents of a file into a byte array.
     * The file is always closed.
     *
     * @param file
     *            the file to read, must not be {@code null}
     * @return the file contents, never {@code null}
     * @throws IOException
     *             in case of an I/O error
     * @since Commons IO 1.1
     */
    private static byte[] readFileToByteArray(final File file) throws IOException {
        InputStream in = null;
        try {
            in = openInputStream(file);
            return IOUtils.toByteArray(in);
        } finally {
            IOUtils.closeQuietly(in);
        }
    }

    /**
     * FROM: Apache
     * Opens a {@link java.io.FileInputStream} for the specified file, providing better
     * error messages than simply calling {@code new FileInputStream(file)}.
     * At the end of the method either the stream will be successfully opened,
     * or an exception will have been thrown.
     * An exception is thrown if the file does not exist.
     * An exception is thrown if the file object exists but is a directory.
     * An exception is thrown if the file exists but cannot be read.
     *
     * @param file
     *            the file to open for input, must not be {@code null}
     * @return a new {@link java.io.FileInputStream} for the specified file
     * @throws java.io.FileNotFoundException
     *             if the file does not exist
     * @throws IOException
     *             if the file object is a directory
     * @throws IOException
     *             if the file cannot be read
     * @since Commons IO 1.3
     */
    private static FileInputStream openInputStream(final File file) throws IOException {
        if (file.exists()) {
            if (file.isDirectory()) {
                throw new IOException("File '" + file + "' exists but is a directory");
            }
            if (file.canRead() == false) {
                throw new IOException("File '" + file + "' cannot be read");
            }
        } else {
            throw new FileNotFoundException("File '" + file + "' does not exist");
        }
        return new FileInputStream(file);
    }

    /**
     * Get the contents of an {@code InputStream} as a {@code byte[]}.
     *
     * @param inputStream
     * @return
     */
    public static byte[] toByteArray(final InputStream inputStream) {
        if (inputStream == null) {
            throw new NullPointerException();
        }
        try {
            final byte[] bytes = IOUtils.toByteArray(inputStream);
            return bytes;
        } catch (IOException e) {
            throw new DSSException(e);
        }
    }

    public static String toString(final byte[] bytes) {

        if (bytes == null) {

            throw new NullPointerException();
        }
        final String string = new String(bytes);
        return string;
    }

    /**
     * This method saves the given array of {@code byte} to the provided {@code File}.
     *
     * @param bytes
     *            to save
     * @param file
     * @throws DSSException
     */
    public static void saveToFile(final byte[] bytes, final File file) throws DSSException {
        file.getParentFile().mkdirs();
        try {
            final FileOutputStream fileOutputStream = new FileOutputStream(file);
            final ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
            IOUtils.copy(inputStream, fileOutputStream);
            IOUtils.closeQuietly(inputStream);
            IOUtils.closeQuietly(fileOutputStream);
        } catch (IOException e) {
            throw new DSSException(e);
        }
    }

    /**
     * This method saves the given {@code InputStream} to a file representing by the provided path. The {@code InputStream} is not closed.
     *
     * @param inputStream
     *            {@code InputStream} to save
     * @param path
     *            the path to the file to be created
     */
    public static void saveToFile(final InputStream inputStream, final String path) throws IOException {
        final FileOutputStream fileOutputStream = toFileOutputStream(path);
        IOUtils.copy(inputStream, fileOutputStream);
        IOUtils.closeQuietly(fileOutputStream);
    }

    public static X509CRL toX509CRL(final X509CRLHolder x509CRLHolder) {
        try {
            final JcaX509CRLConverter jcaX509CRLConverter = new JcaX509CRLConverter();
            final X509CRL x509CRL = jcaX509CRLConverter.getCRL(x509CRLHolder);
            return x509CRL;
        } catch (CRLException e) {
            throw new DSSException(e);
        }
    }

    public static byte[] getEncoded(X509CRL x509CRL) {
        try {
            final byte[] encoded = x509CRL.getEncoded();
            return encoded;
        } catch (CRLException e) {
            throw new DSSException(e);
        }
    }

    /**
     * return a unique id for a date and the certificateToken id.
     *
     * @param signingTime
     * @param id
     * @return
     */
    public static String getDeterministicId(final Date signingTime, TokenIdentifier id) {
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            if (signingTime != null) {
                baos.write(Long.toString(signingTime.getTime()).getBytes());
            }
            if (id != null) {
                baos.write(id.asXmlId().getBytes());
            }
            final String deterministicId = "id-" + getMD5Digest(baos);
            return deterministicId;
        } catch (IOException e) {
            throw new DSSException(e);
        }
    }

    /**
     * Returns a Hex encoded of the MD5 digest of ByteArrayOutputStream
     *
     * @param baos
     * @return
     */
    public static String getMD5Digest(ByteArrayOutputStream baos) {
        try {
            byte[] digestValue = digest(DigestAlgorithm.MD5, baos.toByteArray());
            return Hex.encodeHexString(digestValue);
        } catch (Exception e) {
            throw new DSSException(e);
        }
    }

    public static Date getLocalDate(final Date gtmDate, final Date localDate) {
        final Date newLocalDate = new Date(
                gtmDate.getTime() + TimeZone.getDefault().getOffset(localDate.getTime()));
        return newLocalDate;
    }

    public static long toLong(final byte[] bytes) {
        // Long.valueOf(new String(bytes)).longValue();
        ByteBuffer buffer = ByteBuffer.allocate(8);
        buffer.put(bytes, 0, Long.SIZE / 8);
        // TODO: (Bob: 2014 Jan 22) To be checked if it is not platform dependent?
        buffer.flip();//need flip
        return buffer.getLong();
    }

    public static void delete(final File file) {
        if (file != null) {
            file.delete();
        }
    }

    /**
     * This method returns the {@code X500Principal} corresponding to the given string or {@code null} if the conversion is not possible.
     *
     * @param x500PrincipalString
     *            a {@code String} representation of the {@code X500Principal}
     * @return {@code X500Principal} or null
     */
    public static X500Principal getX500PrincipalOrNull(final String x500PrincipalString) {
        try {
            final X500Principal x500Principal = new X500Principal(x500PrincipalString);
            return x500Principal;
        } catch (Exception e) {
            logger.warn(e.getMessage());
        }
        return null;
    }

    /**
     * This method compares two {@code X500Principal}s. {@code X500Principal.CANONICAL} and {@code X500Principal.RFC2253} forms are compared.
     * TODO: (Bob: 2014 Feb 20) To be investigated why the standard equals does not work!?
     *
     * @param firstX500Principal
     * @param secondX500Principal
     * @return
     */
    public static boolean x500PrincipalAreEquals(final X500Principal firstX500Principal,
            final X500Principal secondX500Principal) {
        if ((firstX500Principal == null) || (secondX500Principal == null)) {
            return false;
        }
        if (firstX500Principal.equals(secondX500Principal)) {
            return true;
        }
        final Map<String, String> firstStringStringHashMap = DSSASN1Utils.get(firstX500Principal);
        final Map<String, String> secondStringStringHashMap = DSSASN1Utils.get(secondX500Principal);
        final boolean containsAll = firstStringStringHashMap.entrySet()
                .containsAll(secondStringStringHashMap.entrySet());

        return containsAll;
    }

    /**
     * @param x509SubjectName
     * @return
     */
    public static X500Principal getX500Principal(String x509SubjectName) throws DSSException {
        try {
            final X500Principal x500Principal = new X500Principal(x509SubjectName);
            return getNormalizedX500Principal(x500Principal);
        } catch (IllegalArgumentException e) {
            throw new DSSException(e);
        }
    }

    /**
     * @param x500Principal
     *            to be normalized
     * @return {@code X500Principal} normalized
     */
    public static X500Principal getNormalizedX500Principal(final X500Principal x500Principal) {
        final String utf8Name = DSSASN1Utils.getUtf8String(x500Principal);
        final X500Principal x500PrincipalNormalized = new X500Principal(utf8Name);
        return x500PrincipalNormalized;
    }

    public static InputStream getResource(final String resourcePath) {
        final InputStream resourceAsStream = DSSUtils.class.getClassLoader().getResourceAsStream(resourcePath);
        return resourceAsStream;
    }

    /**
     * This method returns an UTC date base on the year, the month and the day. The year must be encoded as 1978... and not 78
     *
     * @param year
     *            the value used to set the YEAR calendar field.
     * @param month
     *            the month. Month value is 0-based. e.g., 0 for January.
     * @param day
     *            the value used to set the DAY_OF_MONTH calendar field.
     * @return the UTC date base on parameters
     */
    public static Date getUtcDate(final int year, final int month, final int day) {
        final Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
        calendar.set(year, month, day, 0, 0, 0);
        final Date date = calendar.getTime();
        return date;
    }

    /**
     * This method adds or subtract the given number of days from the date
     *
     * @param date
     *            {@code Date} to change
     * @param days
     *            number of days (can be negative)
     * @return new {@code Date}
     */
    public static Date getDate(final Date date, int days) {

        final Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.add(Calendar.DATE, days);
        final Date newDate = calendar.getTime();
        return newDate;
    }

    public static byte[] getUtf8Bytes(final String string) {

        if (string == null) {
            return null;
        }
        try {
            final byte[] bytes = string.getBytes("UTF-8");
            return bytes;
        } catch (UnsupportedEncodingException e) {
            throw new DSSException(e);
        }
    }

    /**
     * This method return the unique message id which can be used for translation purpose.
     *
     * @param message
     *            the {@code String} message on which the unique id is calculated.
     * @return the unique id
     */
    public static String getMessageId(final String message) {

        final String message_ = message./*replace('\'', '_').*/toLowerCase().replaceAll("[^a-z_]", " ");
        StringBuilder nameId = new StringBuilder();
        final StringTokenizer stringTokenizer = new StringTokenizer(message_);
        while (stringTokenizer.hasMoreElements()) {

            final String word = (String) stringTokenizer.nextElement();
            nameId.append(word.charAt(0));
        }
        final String nameIdString = nameId.toString();
        return nameIdString.toUpperCase();
    }

    /**
     * Returns an estimate of the number of bytes that can be read (or
     * skipped over) from this input stream without blocking by the next
     * invocation of a method for this input stream. The next invocation
     * might be the same thread or another thread. A single read or skip of this
     * many bytes will not block, but may read or skip fewer bytes.
     * the total number of bytes in the stream, many will not. It is
     * never correct to use the return value of this method to allocate
     * a buffer intended to hold all data in this stream. {@link IOException} if this input stream has been closed by
     * invoking the {@link InputStream#close()} method.
     * returns {@code 0}.
     *
     * @return an estimate of the number of bytes that can be read (or skipped
     *         over) from this input stream without blocking or {@code 0} when
     *         it reaches the end of the input stream.
     * @throws DSSException
     *             if IOException occurs (if an I/O error occurs)
     */
    public static int available(final InputStream is) throws DSSException {

        try {
            return is.available();
        } catch (IOException e) {
            throw new DSSException(e);
        }
    }

    public static void copyFile(final String path, final File sourceFile, final File destinationFile)
            throws IOException {

        final File destinationPath = new File(path);
        if (!destinationPath.exists()) {
            destinationPath.mkdirs();
            destinationFile.createNewFile();
        }

        FileChannel source = null;
        FileChannel destination = null;

        try {
            source = new FileInputStream(sourceFile).getChannel();
            destination = new FileOutputStream(destinationFile).getChannel();
            destination.transferFrom(source, 0, source.size());
        } finally {
            if (source != null) {
                source.close();
            }
            if (destination != null) {
                destination.close();
            }
        }
    }

    /**
     * This method closes the given {@code OutputStream} and throws a {@code DSSException} when the operation fails.
     *
     * @param outputStream
     *            {@code OutputStream} to be closed
     */
    public static void close(final OutputStream outputStream) {

        try {
            outputStream.close();
        } catch (IOException e) {
            throw new DSSException(e);
        }
    }

    /**
     * This method lists all defined security providers.
     */
    public static void printSecurityProvides() {

        final Provider[] providers = Security.getProviders();
        for (final Provider provider : providers) {

            System.out.println("PROVIDER: " + provider.getName());
            final Set<Provider.Service> services = provider.getServices();
            for (final Provider.Service service : services) {

                System.out.println("\tALGORITHM: " + service.getAlgorithm() + " / " + service.getType() + " / "
                        + service.getClassName());
            }
        }
    }

    /**
     * This method returns the summary of the given exception. The analysis of the stack trace stops when the provided class is found.
     *
     * @param exception
     *            {@code Exception} to summarize
     * @param javaClass
     *            {@code Class}
     * @return {@code String} containing the summary message
     */
    public static String getSummaryMessage(final Exception exception, final Class<?> javaClass) {

        final String javaClassName = javaClass.getName();
        final StackTraceElement[] stackTrace = exception.getStackTrace();
        String message = "See log file for full stack trace.\n";
        message += exception.toString() + '\n';
        for (StackTraceElement element : stackTrace) {

            final String className = element.getClassName();
            if (className.equals(javaClassName)) {

                message += element.toString() + '\n';
                break;
            }
            message += element.toString() + '\n';
        }
        return message;
    }

    /**
     * Reads maximum {@code headerLength} bytes from {@code dssDocument} to the given {@code byte} array.
     *
     * @param dssDocument
     *            {@code DSSDocument} to read
     * @param headerLength
     *            {@code int}: maximum number of bytes to read
     * @param destinationByteArray
     *            destination {@code byte} array
     * @return
     */
    public static int readToArray(final DSSDocument dssDocument, final int headerLength,
            final byte[] destinationByteArray) {

        final InputStream inputStream = dssDocument.openStream();
        try {
            int read = inputStream.read(destinationByteArray, 0, headerLength);
            return read;
        } catch (IOException e) {
            throw new DSSException(e);
        } finally {
            IOUtils.closeQuietly(inputStream);
        }
    }

    /**
     * Gets a difference between two dates
     *
     * @param date1
     *            the oldest date
     * @param date2
     *            the newest date
     * @param timeUnit
     *            the unit in which you want the diff
     * @return the difference value, in the provided unit
     */
    public static long getDateDiff(final Date date1, final Date date2, final TimeUnit timeUnit) {

        long diff = date2.getTime() - date1.getTime();
        return timeUnit.convert(diff, TimeUnit.MILLISECONDS);
    }

    /**
     * Concatenates all the arrays into a new array. The new array contains all of the element of each array followed by all of the elements of the next array. When an array is
     * returned, it is always a new array.
     *
     * @param arrays
     *            {@code byte} arrays to concatenate
     * @return the new {@code byte} array
     */
    public static byte[] concatenate(byte[]... arrays) {
        if ((arrays == null) || (arrays.length == 0) || ((arrays.length == 1) && (arrays[0] == null))) {
            return null;
        }
        if (arrays.length == 1) {
            return arrays[0].clone();
        }
        int joinedLength = 0;
        for (final byte[] array : arrays) {
            if (array != null) {
                joinedLength += array.length;
            }
        }
        byte[] joinedArray = new byte[joinedLength];
        int destinationIndex = 0;
        for (final byte[] array : arrays) {
            if (array != null) {

                System.arraycopy(array, 0, joinedArray, destinationIndex, array.length);
                destinationIndex += array.length;
            }
        }
        return joinedArray;
    }

    public static String getFinalFileName(DSSDocument originalFile, SigningOperation operation,
            SignatureLevel level) {
        StringBuffer finalName = new StringBuffer();
        String originalName = originalFile.getName();

        if (StringUtils.isNotEmpty(originalName)) {
            int dotPosition = originalName.lastIndexOf('.');
            if (dotPosition > 0) {
                // remove extension
                finalName.append(originalName.substring(0, dotPosition));
            } else {
                finalName.append(originalName);
            }
        } else {
            finalName.append("document");
        }

        if (SigningOperation.SIGN.equals(operation)) {
            finalName.append("-signed-");
        } else if (SigningOperation.EXTEND.equals(operation)) {
            finalName.append("-extended-");
        }

        finalName.append(StringUtils.lowerCase(level.name().replaceAll("_", "-")));
        finalName.append('.');

        SignatureForm signatureForm = level.getSignatureForm();
        switch (signatureForm) {
        case XAdES:
            finalName.append("xml");
            break;
        case CAdES:
            finalName.append("pkcs7");
            break;
        case PAdES:
            finalName.append("pdf");
            break;
        case ASiC_S:
            finalName.append("asics");
            break;
        case ASiC_E:
            finalName.append("asice");
            break;
        default:
            break;
        }

        return finalName.toString();
    }

}