org.signserver.client.cli.defaultimpl.TimeStampCommand.java Source code

Java tutorial

Introduction

Here is the source code for org.signserver.client.cli.defaultimpl.TimeStampCommand.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.client.cli.defaultimpl;

import java.io.*;
import java.math.BigInteger;
import java.net.URL;
import java.net.URLConnection;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Security;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import org.apache.commons.cli.*;
import org.apache.log4j.Logger;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.cmp.PKIFailureInfo;
import org.bouncycastle.cert.AttributeCertificateHolder;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.SignerInformationVerifier;
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.tsp.*;
import org.bouncycastle.util.Selector;
import org.bouncycastle.util.Store;
import org.bouncycastle.util.encoders.Base64;
import org.bouncycastle.util.encoders.Hex;
import org.ejbca.ui.cli.util.ConsolePasswordReader;
import org.signserver.cli.CommandLineInterface;
import org.signserver.cli.spi.AbstractCommand;
import org.signserver.cli.spi.CommandFailureException;
import org.signserver.cli.spi.IllegalCommandArgumentsException;
import org.signserver.cli.spi.UnexpectedCommandFailureException;

/**
 * Class making a simple timestamp request to a timestamp server and tries to
 * validate it.
 *
 *
 * @author philip
 * @version $Id: TimeStampCommand.java 5998 2015-04-29 23:42:03Z netmackan $
 */
public class TimeStampCommand extends AbstractCommand {

    /** Logger for this class. */
    private static final Logger LOG = Logger.getLogger(TimeStampCommand.class);

    /** System-specific new line characters. **/
    private static final String NL = System.getProperty("line.separator");

    /** The name of this command. */
    private static final String COMMAND = "timestamp";

    private static final int PARAM_URL = 0;

    /** Begin key for certificates in PEM format. */
    private static final String PEM_BEGIN = "-----BEGIN CERTIFICATE-----";

    /** End key for certificates in PEM format. */
    private static final String PEM_END = "-----END CERTIFICATE-----";

    private String urlstring;

    private String outrepstring;

    private String inrepstring;

    /** Filename to read a pre-formatted request from. */
    private String inreqstring;

    private String outreqstring;

    private String instring;

    private String infilestring;

    private String signerfilestring;

    private boolean base64;

    private boolean verify;
    private boolean print;

    /** Number of milliseconds to sleep after a request. */
    private int sleep = 1000;

    private boolean certReq;
    private String reqPolicy;

    private final Options options = new Options();

    private final KeyStoreOptions keyStoreOptions = new KeyStoreOptions();

    public TimeStampCommand() {
        // Create options
        final Option help = new Option("help", false, "Print this message.");
        final Option b64 = new Option("base64", false,
                "Give this option if the stored request/reply should be " + "base64 encoded, default is not.");
        final Option verifyopt = new Option("verify", false,
                "Give this option if verification of a stored reply should "
                        + "be done, work together with inrep and cafile. If given, no "
                        + "request to the TSA will happen.");
        final Option printopt = new Option("print", false, "Prints content of a request, response and/or token");

        OptionBuilder.hasArg();
        OptionBuilder.withDescription("Url of TSA, e.g. " + "http://127.0.0.1:8080/signserver/process?workerId=1.");
        OptionBuilder.withArgName("url");
        final Option url = OptionBuilder.create("url");

        OptionBuilder.hasArg();
        OptionBuilder.withArgName("file");
        OptionBuilder.withDescription(
                "Output file to store the recevied TSA " + "reply, if not given the reply is not stored.");
        final Option outrep = OptionBuilder.create("outrep");

        OptionBuilder.hasArg();
        OptionBuilder.withArgName("file");
        OptionBuilder.withDescription("Input file containing an earlier stored "
                + "base64 encoded response, to verify." + "You must specify the verify flag also.");
        final Option inrep = OptionBuilder.create("inrep");

        OptionBuilder.hasArg();
        OptionBuilder.withArgName("file");
        OptionBuilder.withDescription("Input file containing the PEM encoded " + "certificate of the TSA signer."
                + "Used to verify a stored response.");
        final Option cafileopt = OptionBuilder.create("signerfile");

        OptionBuilder.hasArg();
        OptionBuilder.withArgName("file");
        OptionBuilder.withDescription(
                "Output file to store the sent TSA " + "request, if not given the request is not stored.");
        final Option outreq = OptionBuilder.create("outreq");

        OptionBuilder.hasArg();
        OptionBuilder.withArgName("file");
        OptionBuilder.withDescription("File containing message to time stamp.");
        final Option infile = OptionBuilder.create("infile");

        OptionBuilder.hasArg();
        OptionBuilder.withArgName("string");
        OptionBuilder.withDescription("String to be time stamped, if neither "
                + "instr or infile is given, the client works in test-mode " + "generating it's own message.");
        final Option instr = OptionBuilder.create("instr");

        OptionBuilder.hasArg();
        OptionBuilder.withArgName("file");
        OptionBuilder.withDescription("Input file containing an earlier stored "
                + "request to use instead of creating a new. " + "You must specify the request flag also.");
        final Option inreq = OptionBuilder.create("inreq");

        OptionBuilder.hasArg();
        OptionBuilder.withArgName("num");
        OptionBuilder.withDescription("Sleep a number of milliseconds after " + "each request. Default 1000 ms.");
        final Option optionSleep = OptionBuilder.create("sleep");

        OptionBuilder.hasArg(false);
        OptionBuilder.withDescription("Request signer certificate");
        final Option certReqOption = OptionBuilder.create("certreq");

        OptionBuilder.hasArg();
        OptionBuilder.withArgName("oid");
        OptionBuilder.withDescription("Request timestamp issued under a policy OID");
        final Option reqPolicyOption = OptionBuilder.create("reqpolicy");

        // Add options
        options.addOption(help);
        options.addOption(verifyopt);
        options.addOption(printopt);
        options.addOption(url);
        options.addOption(outrep);
        options.addOption(inrep);
        options.addOption(cafileopt);
        options.addOption(outreq);
        options.addOption(b64);
        options.addOption(infile);
        options.addOption(instr);
        options.addOption(inreq);
        options.addOption(optionSleep);
        options.addOption(certReqOption);
        options.addOption(reqPolicyOption);

        for (Option option : KeyStoreOptions.getKeyStoreOptions()) {
            options.addOption(option);
        }
    }

    @Override
    public String getDescription() {
        return "Send time stamp requests to a TSA";
    }

    @Override
    public String getUsages() {
        return usage(options);
    }

    private String usage(final Options options) {
        // automatically generate the help statement
        final HelpFormatter formatter = new HelpFormatter();
        formatter.printHelp("timestamp <options> [url]", options);
        final StringBuilder footer = new StringBuilder();
        footer.append(NL).append("Sample usages:").append(NL).append("a) ").append(COMMAND)
                .append(" -url http://localhost:8080/signserver/tsa?workerName=TimeStampSigner").append(NL)
                .append("b) ").append(COMMAND).append(" -print -inreq query.tsq").append(NL).append("c) ")
                .append(COMMAND).append(" -print -inrep reply.tsr").append(NL);
        return footer.toString();
    }

    @Override
    public int execute(String... args)
            throws IllegalCommandArgumentsException, CommandFailureException, UnexpectedCommandFailureException {

        final CommandLineParser parser = new GnuParser();
        try {
            final CommandLine cmd = parser.parse(options, args);
            if (cmd.hasOption("help")) {
                out.println(usage(options));
                return CommandLineInterface.RETURN_SUCCESS;
            }
            if (cmd.hasOption("url")) {
                urlstring = cmd.getOptionValue("url");
            }
            if (cmd.hasOption("instr")) {
                instring = cmd.getOptionValue("instr");
            }
            if (cmd.hasOption("infile")) {
                infilestring = cmd.getOptionValue("infile");
            }
            if (cmd.hasOption("outrep")) {
                outrepstring = cmd.getOptionValue("outrep");
            }
            if (cmd.hasOption("inrep")) {
                inrepstring = cmd.getOptionValue("inrep");
            }
            if (cmd.hasOption("signerfile")) {
                signerfilestring = cmd.getOptionValue("signerfile");
            }
            if (cmd.hasOption("outreq")) {
                outreqstring = cmd.getOptionValue("outreq");
            }
            if (cmd.hasOption("base64")) {
                base64 = true;
            }
            if (cmd.hasOption("verify")) {
                verify = true;
            }
            if (cmd.hasOption("print")) {
                print = true;
            }
            if (cmd.hasOption("inreq")) {
                inreqstring = cmd.getOptionValue("inreq");
            }
            if (cmd.hasOption("sleep")) {
                sleep = Integer.parseInt(cmd.getOptionValue("sleep"));
            }
            final String[] strargs = cmd.getArgs();
            if (strargs.length > 0) {
                urlstring = strargs[PARAM_URL];
            }
            if (cmd.hasOption("certreq")) {
                certReq = true;
            }
            if (cmd.hasOption("reqpolicy")) {
                reqPolicy = cmd.getOptionValue("reqpolicy");
            }

            try {
                final ConsolePasswordReader passwordReader = createConsolePasswordReader();
                keyStoreOptions.parseCommandLine(cmd, passwordReader, out);

                // TODO: Add when implementing username/password auth support:
                // Prompt for user password if not given
                //if (username != null && password == null) {
                //    out.print("Password for user '" + username + "': ");
                //    out.flush();
                //    password = new String(passwordReader.readPassword());
                //}
            } catch (IOException ex) {
                throw new IllegalCommandArgumentsException("Failed to read password: " + ex.getLocalizedMessage());
            }

            if (print && inreqstring == null && inrepstring == null) {
                LOG.error("Missing -inreq or -inrep");
                out.println(usage(options));
                return CommandLineInterface.RETURN_INVALID_ARGUMENTS;
            }

            if (args.length < 1) {
                out.println(usage(options));
                return CommandLineInterface.RETURN_INVALID_ARGUMENTS;
            } else if (urlstring == null && !verify && !print) {
                LOG.error("Missing URL");
                out.println(usage(options));
                return CommandLineInterface.RETURN_INVALID_ARGUMENTS;
            } else {
                keyStoreOptions.validateOptions();

                if (Security.addProvider(new BouncyCastleProvider()) < 0) {
                    LOG.error("Could not install BC provider");
                    // If already installed, remove so we can handle redeploy
                    Security.removeProvider("BC");
                    if (Security.addProvider(new BouncyCastleProvider()) < 0) {
                        LOG.error("Cannot even install BC provider again!");
                    }
                }

                run();
                return CommandLineInterface.RETURN_SUCCESS;
            }
        } catch (ParseException e) {
            // oops, something went wrong
            out.println(usage(options));
            return CommandLineInterface.RETURN_INVALID_ARGUMENTS;
        } catch (Exception ex) {
            throw new UnexpectedCommandFailureException(ex);
        }
    }

    /**
     * @return a ConsolePasswordReader that can be used to read passwords
     */
    protected ConsolePasswordReader createConsolePasswordReader() {
        return new ConsolePasswordReader();
    }

    private void run() throws Exception {
        // Take start time
        final long startTime = System.nanoTime();

        if (print) {
            tsaPrint();
        } else if (verify) {
            tsaVerify();
        } else {
            tsaRequest();
        }

        // Take stop time
        final long estimatedTime = System.nanoTime() - startTime;

        LOG.info("Processing took " + TimeUnit.NANOSECONDS.toMillis(estimatedTime) + " ms");
    }

    private void tsaPrint() throws Exception {

        if (inrepstring == null) {
            tsaPrintQuery();
        } else {
            tsaPrintReply();
        }
    }

    private void tsaPrintReply() throws Exception {
        final byte[] bytes = readFiletoBuffer(inrepstring);

        TimeStampResponse response = null;
        out.println("Time-stamp response {");
        try {
            response = new TimeStampResponse(bytes);
            out.println("  Status:                           " + response.getStatus());
            out.println("  Status message:                   " + response.getStatusString());
        } catch (TSPException ex) {
            out.println("  Not a response");
        }
        if (response != null) {
            PKIFailureInfo failureInfo = response.getFailInfo();
            if (failureInfo != null) {
                out.print("  Failure info:                          ");
                out.println(failureInfo.intValue());
            }
        }

        final TimeStampToken token;
        if (response == null) {
            token = new TimeStampToken(new CMSSignedData(bytes));
        } else {
            token = response.getTimeStampToken();
        }
        if (token != null) {
            out.println("  Time-stamp token:");
            TimeStampTokenInfo info = token.getTimeStampInfo();
            if (info != null) {
                out.println("      Info:");
                out.print("         " + "Accuracy:                  ");
                out.println(info.getAccuracy() != null ? info.getAccuracy() : "(null)");

                out.print("         " + "Gen Time:                  ");
                out.println(info.getGenTime());

                out.print("         " + "Gen Time Accuracy:         ");
                out.println(info.getGenTimeAccuracy());

                out.print("         " + "Message imprint digest:    ");
                out.println(new String(Hex.encode(info.getMessageImprintDigest())));

                out.print("         " + "Message imprint algorithm: ");
                out.println(info.getMessageImprintAlgOID());

                out.print("         " + "Nonce:                     ");
                out.println(info.getNonce() != null ? info.getNonce().toString(16) : "(null)");

                out.print("         " + "Serial Number:             ");
                out.println(info.getSerialNumber() != null ? info.getSerialNumber().toString(16) : "(null)");

                out.print("         " + "TSA:                       ");
                out.println(info.getTsa() != null ? info.getTsa() : "(null)");

                out.print("         " + "Policy:                    ");
                out.println(info.getPolicy());
            }
            out.println("      Signer ID: ");
            out.println("         Serial Number:             " + token.getSID().getSerialNumber().toString(16));
            out.println("         Issuer:                    " + token.getSID().getIssuer());

            out.println("      Signer certificate:           ");

            Store certs = token.getCertificates();
            Selector signerSelector = new AttributeCertificateHolder(token.getSID().getIssuer(),
                    token.getSID().getSerialNumber());

            Collection certCollection = certs.getMatches(signerSelector);
            for (Object o : certCollection) {
                if (o instanceof X509CertificateHolder) {
                    X509CertificateHolder cert = (X509CertificateHolder) o;
                    out.println("         Certificate: ");
                    out.println("            Serial Number:          " + cert.getSerialNumber().toString(16));
                    out.println("            Subject:                " + cert.getSubject());
                    out.println("            Issuer:                 " + cert.getIssuer());
                } else {
                    out.println("Not an X.509 certificate: " + o);
                }
            }

            out.println("      Other certificates: ");
            certCollection = certs.getMatches(new InvertedSelector(signerSelector));
            for (Object o : certCollection) {
                if (o instanceof X509CertificateHolder) {
                    X509CertificateHolder cert = (X509CertificateHolder) o;
                    out.println("         Certificate: ");
                    out.println("            Serial Number:          " + cert.getSerialNumber().toString(16));
                    out.println("            Subject:                " + cert.getSubject());
                    out.println("            Issuer:                 " + cert.getIssuer());
                } else {
                    out.println("Not an X.509 certificate: " + o);
                }
            }
        }
        out.println("}");
    }

    private void tsaPrintQuery() throws Exception {
        final byte[] bytes = readFiletoBuffer(inreqstring);

        final TimeStampRequest request;
        out.println("Time-stamp request {");

        request = new TimeStampRequest(bytes);
        out.println("  Version:                          " + request.getVersion());

        out.print("  Message imprint digest:           ");
        out.println(new String(Hex.encode(request.getMessageImprintDigest())));

        out.print("  Message imprint algorithm:        ");
        out.println(request.getMessageImprintAlgOID());

        out.print("  Policy:                           ");
        out.println(request.getReqPolicy() != null ? request.getReqPolicy() : "(null)");

        out.print("  Nonce:                            ");
        out.println(request.getNonce() != null ? request.getNonce().toString(16) : "(null)");

        out.print("  Request certificates:             ");
        out.println(request.getCertReq());

        if (request.hasExtensions()) {
            out.print("  Extensions: ");
            for (Object oid : request.getExtensionOIDs()) {
                if (oid instanceof String) {
                    out.print("    " + oid + ": ");
                    out.println(new String(Hex.encode(request.getExtensionValue((String) oid))));
                }
            }
        }

        out.println("}");
    }

    private static class InvertedSelector implements Selector {

        private final Selector delegate;

        public InvertedSelector(Selector delegate) {
            this.delegate = delegate;
        }

        @Override
        public boolean match(Object cert) {
            return !delegate.match(cert);
        }

        @Override
        @SuppressWarnings({ "CloneDeclaresCloneNotSupported", "CloneDoesntCallSuperClone" }) // Selector interface does not declare CloneNotSupported
        public Object clone() {
            return new InvertedSelector((Selector) delegate.clone());
        }

    }

    private void tsaVerify() throws Exception {
        if (inrepstring == null) {
            LOG.error("Needs an inrep!");
        } else if (signerfilestring == null) {
            LOG.error("Needs a signerfile!");
        } else {
            final Collection<X509Certificate> col = getCertsFromPEM(signerfilestring);
            final X509Certificate[] list = (X509Certificate[]) col.toArray(new X509Certificate[0]);
            if (list.length == 0) {
                LOG.error("No certificate found in file: " + signerfilestring);
                return;
            }

            final byte[] b64Bytes = readFiletoBuffer(inrepstring);
            final byte[] replyBytes = Base64.decode(b64Bytes);

            final TimeStampResponse timeStampResponse = new TimeStampResponse(replyBytes);
            final TimeStampToken token = timeStampResponse.getTimeStampToken();
            final SignerInformationVerifier infoVerifier = new JcaSimpleSignerInfoVerifierBuilder()
                    .setProvider("BC").build(list[0]);
            token.validate(infoVerifier);
            LOG.info("Token was validated successfully.");

            final TimeStampTokenInfo info = token.getTimeStampInfo();
            LOG.info("Token was generated on: " + info.getGenTime());

            if (LOG.isDebugEnabled()) {
                if (info.getMessageImprintAlgOID().equals(TSPAlgorithms.SHA1)) {
                    LOG.debug("Token hash alg: SHA1");
                } else {
                    LOG.debug("Token hash alg: " + info.getMessageImprintAlgOID());
                }
            }
            final byte[] hexDigest = Hex.encode(info.getMessageImprintDigest());
            LOG.info("MessageDigest=" + new String(hexDigest));
        }
    }

    @SuppressWarnings("SleepWhileInLoop") // We are just using the sleep for rate limiting
    private void tsaRequest() throws Exception {
        final Random rand = new Random();
        final TimeStampRequestGenerator timeStampRequestGenerator = new TimeStampRequestGenerator();
        boolean doRun = true;
        do {

            final int nonce = rand.nextInt();

            byte[] digest = new byte[20];
            if (instring != null) {
                final byte[] digestBytes = instring.getBytes("UTF-8");
                final MessageDigest dig = MessageDigest.getInstance(TSPAlgorithms.SHA1.getId(), "BC");
                dig.update(digestBytes);
                digest = dig.digest();
                // When we have given input, we don't want to loop
                doRun = false;
            }
            if (infilestring != null) {
                // TSPAlgorithms constants changed from Strings to ASN1Encoded objects
                digest = digestFile(infilestring, TSPAlgorithms.SHA1.getId());
                doRun = false;
            }
            final byte[] hexDigest = Hex.encode(digest);

            if (LOG.isDebugEnabled()) {
                LOG.debug("MessageDigest=" + new String(hexDigest));
            }

            final TimeStampRequest timeStampRequest;
            if (inreqstring == null) {
                LOG.debug("Generating a new request");
                timeStampRequestGenerator.setCertReq(certReq);
                if (reqPolicy != null) {
                    timeStampRequestGenerator.setReqPolicy(new ASN1ObjectIdentifier(reqPolicy));
                }
                timeStampRequest = timeStampRequestGenerator.generate(TSPAlgorithms.SHA1, digest,
                        BigInteger.valueOf(nonce));
            } else {
                LOG.debug("Reading request from file");
                timeStampRequest = new TimeStampRequest(readFiletoBuffer(inreqstring));
            }
            final byte[] requestBytes = timeStampRequest.getEncoded();

            if (outreqstring != null) {
                // Store request
                byte[] outBytes;
                if (base64) {
                    outBytes = Base64.encode(requestBytes);
                } else {
                    outBytes = requestBytes;
                }
                FileOutputStream fos = null;
                try {
                    fos = new FileOutputStream(outreqstring);
                    fos.write(outBytes);
                } finally {
                    if (fos != null) {
                        fos.close();
                    }
                }
            }

            keyStoreOptions.setupHTTPS();

            URL url;
            URLConnection urlConn;
            DataOutputStream printout;
            DataInputStream input;

            url = new URL(urlstring);

            // Take start time
            final long startMillis = System.currentTimeMillis();
            final long startTime = System.nanoTime();
            if (LOG.isDebugEnabled()) {
                LOG.debug("Sending request at: " + startMillis);
            }

            urlConn = url.openConnection();

            urlConn.setDoInput(true);
            urlConn.setDoOutput(true);
            urlConn.setUseCaches(false);
            urlConn.setRequestProperty("Content-Type", "application/timestamp-query");

            // Send POST output.
            printout = new DataOutputStream(urlConn.getOutputStream());
            printout.write(requestBytes);
            printout.flush();
            printout.close();

            // Get response data.
            input = new DataInputStream(urlConn.getInputStream());

            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int b;
            while ((b = input.read()) != -1) {
                baos.write(b);
            }

            // Take stop time
            final long estimatedTime = System.nanoTime() - startTime;

            LOG.info("Got reply after " + TimeUnit.NANOSECONDS.toMillis(estimatedTime) + " ms");

            final byte[] replyBytes = baos.toByteArray();
            if (outrepstring != null) {
                // Store request
                byte[] outBytes;
                if (base64) {
                    outBytes = Base64.encode(replyBytes);
                } else {
                    outBytes = replyBytes;
                }
                FileOutputStream fos = null;
                try {
                    fos = new FileOutputStream(outrepstring);
                    fos.write(outBytes);
                } finally {
                    if (fos != null) {
                        fos.close();
                    }
                }
            }

            final TimeStampResponse timeStampResponse = new TimeStampResponse(replyBytes);
            timeStampResponse.validate(timeStampRequest);

            LOG.info("TimeStampRequest validated");

            if (LOG.isDebugEnabled()) {
                final Date genTime;
                if (timeStampResponse.getTimeStampToken() != null
                        && timeStampResponse.getTimeStampToken().getTimeStampInfo() != null) {
                    genTime = timeStampResponse.getTimeStampToken().getTimeStampInfo().getGenTime();
                } else {
                    genTime = null;
                }
                LOG.debug("(Status: " + timeStampResponse.getStatus() + ", " + timeStampResponse.getFailInfo()
                        + "): " + timeStampResponse.getStatusString()
                        + (genTime != null ? (", genTime: " + genTime.getTime()) : "") + "\n");

            }

            if (doRun) {
                Thread.sleep(sleep);
            }
        } while (doRun);
    }

    /**
     * Helpfunction to read a file to a byte array.
     *
     * @param file filename of file.
     * @return byte[] containing the contents of the file.
     * @throws IOException if the file does not exist or cannot be read.
     */
    private byte[] readFiletoBuffer(final String file) throws IOException {

        ByteArrayOutputStream os = null;
        InputStream in = null;
        try {
            os = new ByteArrayOutputStream();
            in = new FileInputStream(file);
            int len;
            final byte[] buf = new byte[1024];

            while ((len = in.read(buf)) > 0) {
                os.write(buf, 0, len);
            }

            return os.toByteArray();
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException ex) {
                    ex.printStackTrace(System.err);
                }
            }
            if (os != null) {
                try {
                    os.close();
                } catch (IOException ex) {
                    ex.printStackTrace(System.err);
                }
            }
        }
    }

    /**
     * Helpfunction to calculate the digest of a big file.
     *
     * @param file filename of file.
     * @param digestAlg the digest algorithm.
     * @return byte[] containing the digest of the file.
     * @throws IOException if the file does not exist or cannot be read.
     * @throws NoSuchProviderException if BC provider is not installed
     * @throws NoSuchAlgorithmException if the given hash algorithm does not
     * exist
     */
    private byte[] digestFile(final String file, final String digestAlg)
            throws IOException, NoSuchAlgorithmException, NoSuchProviderException {

        final MessageDigest dig = MessageDigest.getInstance(digestAlg, "BC");

        InputStream in = null;
        try {
            in = new FileInputStream(file);

            final byte[] buf = new byte[2048];
            int len;
            while ((len = in.read(buf)) > 0) {
                dig.update(buf, 0, len);
            }

            return dig.digest();
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException ex) {
                    ex.printStackTrace(System.err);
                }
            }
        }
    }

    /**
     * Reads a certificate in PEM-format from a file.
     *
     * The file may contain other things, the first certificate in the file is
     * read.
     *
     * @param certFile the file containing the certificate in PEM-format
     * @return Ordered List of X509Certificate, first certificate first,
     * or empty List
     * @exception IOException if the filen cannot be read.
     * @exception CertificateException if the filen does not contain a correct
     * certificate.
     */
    private List<X509Certificate> getCertsFromPEM(final String certFile) throws IOException, CertificateException {
        InputStream inStrm = null;
        try {
            inStrm = new FileInputStream(certFile);
            return getCertsFromPEM(inStrm);
        } finally {
            if (inStrm != null) {
                inStrm.close();
            }
        }
    }

    /**
     * Reads a certificate in PEM-format from an InputStream.
     *
     * The stream may contain other things, the first certificate in the
     * stream is read.
     *
     * @param certstream the input stream containing the certificate in
     * PEM-format
     * @return Ordered List of X509Certificate, first certificate first,
     * or empty List
     * @exception IOException if the stream cannot be read.
     * @exception CertificateException if the stream does not contain a
     * correct certificate.
     */
    private List<X509Certificate> getCertsFromPEM(final InputStream certstream)
            throws IOException, CertificateException {
        final ArrayList<X509Certificate> ret = new ArrayList<X509Certificate>();

        final BufferedReader bufRdr = new BufferedReader(new InputStreamReader(certstream));

        while (bufRdr.ready()) {
            final ByteArrayOutputStream ostr = new ByteArrayOutputStream();
            final PrintStream opstr = new PrintStream(ostr);
            String temp;
            while ((temp = bufRdr.readLine()) != null && !temp.equals(PEM_BEGIN)) {
            }
            if (temp == null) {
                throw new IOException("Error in " + certstream.toString() + ", missing " + PEM_BEGIN + " boundary");
            }
            while ((temp = bufRdr.readLine()) != null && !temp.equals(PEM_END)) {
                opstr.print(temp);
            }
            if (temp == null) {
                throw new IOException("Error in " + certstream.toString() + ", missing " + PEM_END + " boundary");
            }
            opstr.close();

            final byte[] certbuf = Base64.decode(ostr.toByteArray());
            ostr.close();
            // Phweeew, were done, now decode the cert from file back to
            // X509Certificate object
            final CertificateFactory cf = getCertificateFactory();
            final X509Certificate x509cert = (X509Certificate) cf
                    .generateCertificate(new ByteArrayInputStream(certbuf));
            ret.add(x509cert);
        }
        return ret;
    }

    private CertificateFactory getCertificateFactory() {
        try {
            return CertificateFactory.getInstance("X.509", "BC");
        } catch (NoSuchProviderException nspe) {
            LOG.error("Error creating certificate factory", nspe);
        } catch (CertificateException ce) {
            LOG.error("Error creating certificate factory", ce);
        }
        return null;
    }
}