net.sf.dsig.DSApplet.java Source code

Java tutorial

Introduction

Here is the source code for net.sf.dsig.DSApplet.java

Source

/*
 * Copyright 2007-2014 Anestis Georgiadis
 *  
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and 
 * limitations under the License.
 */

package net.sf.dsig;

import net.sf.dsig.helpers.KeyUsageHelper;
import net.sf.dsig.helpers.UserHomeSettingsParser;
import net.sf.dsig.keystores.KeyStoreProxy;
import net.sf.dsig.keystores.KeyStoreProxyFactory;
import org.apache.commons.lang.StringEscapeUtils;
import org.slf4j.LoggerFactory;
import org.slf4j.profiler.Profiler;

import javax.swing.*;
import java.awt.*;
import java.awt.Dialog.ModalityType;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.math.BigInteger;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.X509Certificate;
import java.util.*;
import java.util.concurrent.Semaphore;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;

public class DSApplet extends JApplet {

    private static final long serialVersionUID = -7671795492911882803L;

    private static final org.slf4j.Logger logger = LoggerFactory.getLogger(DSApplet.class);

    private static final String DSAPPLET_VERSION = "2.3.0-SNAPSHOT";

    private static final Profiler initProfiler = new Profiler("INITIALIZATION");

    static {
        initProfiler.start("Initializing");
    }

    /**
     * The background color of the applet
     */
    private String backgroundColor = "#FFFFFF";

    public void setBackgroundColor(String backgroundColor) {
        this.backgroundColor = backgroundColor;
    }

    /**
     * The id of the form to sign; if set, applet will render itself as a
     * button, that will submit the form with the corresponding id.
     */
    private String formId = null;

    public void setFormId(String formId) {
        if (formId == null || formId.length() == 0) {
            this.formId = null;
        } else {
            this.formId = formId;
        }
    }

    /**
     * <p>The name of the JavaScript function to invoke on successful completion
     * of the digital signing process.
     * 
     * <p>The method is called as follows:
     * <pre><i>successJSFunction();
     */
    private String successJSFunction = null;

    public void setSuccessJSFunction(String successJSFunction) {
        this.successJSFunction = successJSFunction;
    }

    /**
     * <p>The name of the JavaScript function to invoke when an error occurs
     * during the digital signing process.
     * 
     * <p>The method is called as follows:
     * <pre><i>errorJSFunction(errorCode);
     */
    private String errorJSFunction = null;

    public void setErrorJSFunction(String errorJSFunction) {
        this.errorJSFunction = errorJSFunction;
    }

    /**
     * <p>The name of the JavaScript function to invoke when no certificates
     * are available for performing a digital signature.
     * 
     * <p>The method is called as follows:
     * <pre><i>noCertificatesJSFunction();
     */
    private String noCertificatesJSFunction = null;

    public void setNoCertificatesJSFunction(String noCertificatesJSFunction) {
        this.noCertificatesJSFunction = noCertificatesJSFunction;
    }

    /**
     * <p>The name of the JavaScript function to invoke when the applet
     * has started.
     * 
     * <p>The method is called as follows:
     * <pre><i>startedJSFunction();
     */
    private String startedJSFunction = null;

    public void setStartedJSFunction(String startedJSFunction) {
        this.startedJSFunction = startedJSFunction;
    }

    /**
     * <p>A regular expression that is tested against the subject of the
     * certificate. When set only matching certificates are accepted
     * and displayed for selection.
     */
    private String subjectMatchingRegex = null;

    public void setSubjectMatchingRegex(String subjectRegex) {
        this.subjectMatchingRegex = subjectRegex;
    }

    private Pattern subjectMatchingPattern = null;

    private Pattern getSubjectMatchingPattern() {
        if (subjectMatchingPattern == null && subjectMatchingRegex != null) {
            subjectMatchingPattern = Pattern.compile(subjectMatchingRegex);
        }

        return subjectMatchingPattern;
    }

    /**
     * <p>A regular expression that is tested against the issuer
     * of the certificate. When set only matching certificates are accepted
     * and displayed for selection. 
     */
    private String issuerMatchingRegex = null;

    public void setIssuerMatchingRegex(String issuerMatchingRegex) {
        this.issuerMatchingRegex = issuerMatchingRegex;
    }

    private Pattern issuerMatchingPattern = null;

    private Pattern getIssuerMatchingPattern() {
        if (issuerMatchingPattern == null && issuerMatchingRegex != null) {
            issuerMatchingPattern = Pattern.compile(issuerMatchingRegex);
        }

        return issuerMatchingPattern;
    }

    /**
     * A comma-separated list of serial numbers, for testing against each
     * certificate's serial number. When set only matching certificates are
     * accepted and displayed for selection.
     */
    private String serialNumbersAllowed = null;

    public void setSerialNumbersAllowed(String serialNumbersAllowed) {
        this.serialNumbersAllowed = serialNumbersAllowed;
    }

    private Set<BigInteger> serialNumbersAllowedSet = null;

    public Set<BigInteger> getSerialNumbersAllowedSet() {
        if (serialNumbersAllowedSet == null && serialNumbersAllowed != null) {
            String[] serialNumbers = serialNumbersAllowed.split(",");
            serialNumbersAllowedSet = new HashSet<BigInteger>();
            for (String serialNumber : serialNumbers) {
                serialNumbersAllowedSet.add(new BigInteger(serialNumber));
            }
        }

        return serialNumbersAllowedSet;
    }

    /** Flag controlling whether the expiration date is checked */
    private boolean expirationDateChecked = true;

    public void setExpirationDateChecked(boolean expirationDateChecked) {
        this.expirationDateChecked = expirationDateChecked;
    }

    /** A comma-separated list of KeyUsage attributes to check. When set
     * only certificates containing all of the required attributes will be
     * accepted and displayed for selection.
     */
    private String keyUsageRestrictions = null;

    public void setKeyUsageRestrictions(String keyUsageRestrictions) {
        this.keyUsageRestrictions = keyUsageRestrictions;
    }

    /** Flag controlling whether the status bar message is shown */
    private boolean statusBarMessageShown = true;

    public void setStatusBarMessageShown(boolean statusBarMessageShown) {
        this.statusBarMessageShown = statusBarMessageShown;
    }

    private String userAgent;

    public void setUserAgent(String userAgent) {
        this.userAgent = userAgent;
    }

    public String getUserAgent() {
        return userAgent;
    }

    @Override
    public String[][] getParameterInfo() {
        return new String[][] {
                { "backgroundColor", "String",
                        "background color code, in hexadecimal format; defaults to white (#FFFFFF)" },
                { "formId", "String", "id of the HTML form to digitally sign" },
                { "successJSFunction", "String",
                        "name of JS function to execute on succesful signing; called with no arguments (i.e. onSuccess(); )" },
                { "errorJSFunction", "String",
                        "name of JS function to execute on failed signing; called with no arguments (i.e. onError(); )" },
                { "noCertificatesJSFunction", "String",
                        "name of JS function to execute when no certificates exist; called with no arguments (i.e. onNoCertificates(); )" },
                { "startedJSFunction", "String",
                        "name of JS function to execute when applet has started; called with no arguments (i.e. onNoCertificates(); )" },
                { "issuerNameRegex", "String", "regular expression to match issuer's name for acceptance" },
                { "subjectNameRegex", "String", "regular expression to match certificate's name in subject" },
                { "subjectFriendlyRegex", "String",
                        "regular expression to match certificate's friendly name in subject" },
                { "serialNumbersAllowed", "String, comma-delimited",
                        "list of serial numbers to allow for selection" },
                { "expirationDateChecked", "boolean",
                        "true to check certificate's expiration date (default); false otherwise" },
                { "keyUsageRestrictions", "String", "list of required key usage purposes" },
                { "statusBarMessageShown", "String",
                        "true to display in the status bar the version message (default); false otherwise" },
                { "userAgent", "String", "Enclosing browser's user agent" }, };
    }

    // Only lookup .properties files
    private ResourceBundle messages = ResourceBundle.getBundle("messages",
            ResourceBundle.Control.getControl(ResourceBundle.Control.FORMAT_PROPERTIES));

    // The synchronization lock; created with zero permits, the one and
    // only available permit will be released when initialization is completed
    private final Semaphore available = new Semaphore(0);

    private void initSwing() {
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (Exception e) {
            logger.warn("UIManager.setLookAndFeel() failed", e);
        }

        JPanel panel = new JPanel();
        panel.setLayout(new BoxLayout(panel, BoxLayout.LINE_AXIS));
        panel.setBackground(Color.decode(backgroundColor));
        add(panel);

        boolean lockPrinted = false;
        if (formId != null) {
            Icon lockIcon = new ImageIcon(getClass().getResource("/icons/lock.png"));
            lockPrinted = true;

            JButton button = new JButton("Sign", lockIcon);
            button.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    available.acquireUninterruptibly();
                    try {
                        signInternal(formId, null);
                    } catch (Exception ex) {
                        logger.error("Internal sign failed", e);
                    } finally {
                        available.release();
                    }
                }
            });
            panel.add(button);
        }

        Icon infoIcon = new ImageIcon(getClass().getResource(lockPrinted ? "/icons/info.png" : "/icons/lock.png"));
        JLabel infoLabel = new JLabel(infoIcon);
        infoLabel.addMouseListener(new MouseListener() {
            public void mouseClicked(MouseEvent e) {
                JOptionPane.showMessageDialog(null, printInfoMessage());
            }

            public void mouseEntered(MouseEvent e) {
                /* NOOP */ }

            public void mouseExited(MouseEvent e) {
                /* NOOP */ }

            public void mousePressed(MouseEvent e) {
                /* NOOP */ }

            public void mouseReleased(MouseEvent e) {
                /* NOOP */ }
        });
        panel.add(infoLabel);
    }

    private StrategyFactory strategyFactory;

    private KeyStoreProxy keyStoreProxy = null;
    private Exception createKeyStoreProxyException = null;

    private KeyStoreProxy getKeyStoreProxy() {
        if (createKeyStoreProxyException != null) {
            handleError("DSA0001", createKeyStoreProxyException);
        }

        return keyStoreProxy;
    }

    @Override
    public void init() {
        super.init();

        // StrategyFactory service loader ----------------------------------- //
        strategyFactory = ServiceLoader.load(StrategyFactory.class).iterator().next();

        // Environment initialization --------------------------------------- //
        initProfiler.start("Environment init");
        Environment.getSingleton().setApplet(this);
        Environment.getSingleton().setProperties(UserHomeSettingsParser.parse());

        // Set the default java.logging logger for FINEST logging on
        // gr.ageorgiadis package when debug environmental parameter is set
        initProfiler.start("Logging init");
        if (Boolean.parseBoolean(Environment.getSingleton().getValue("debug"))) {
            System.out.println("\n*** Debug log enabled ***");

            Logger.getLogger("").getHandlers()[0].setLevel(Level.FINEST);
            Logger.getLogger("").setLevel(Level.INFO);

            Logger.getLogger("net.sf.dsig").setLevel(Level.FINEST);
        }

        // Applet initialization through the Environment class -------------- //
        initProfiler.start("Applet init");
        Environment.getSingleton().init(this);

        // LiveConnect proxy initialization --------------------------------- //
        LiveConnectProxy.getSingleton().setApplet(this);

        // KeyStoreProxy initialization ------------------------------------- //
        try {
            KeyStoreProxyFactory factory = new KeyStoreProxyFactory();
            Environment.getSingleton().init(factory);

            keyStoreProxy = factory.createKeyStoreProxy();
        } catch (Exception e) {
            logger.error("Error while creating keyStoreProxy", e);

            createKeyStoreProxyException = e;
        }

        // Swing initialization --------------------------------------------- //
        initProfiler.start("Swing init");
        try {
            SwingUtilities.invokeAndWait(new Runnable() {
                public void run() {
                    initSwing();
                }
            });
        } catch (Exception e) {
            logger.error("Swing initialization failed", e);
        }

        initProfiler.start("Starting");
    }

    @Override
    public void start() {
        available.release();

        initProfiler.setLogger(logger);

        initProfiler.stop().log();

        if (startedJSFunction != null) {
            LiveConnectProxy.getSingleton().eval(startedJSFunction + "();");
        }

        // Add a small delay before performing the started operations (in
        // the case of status printing it will otherwise be overridden by 
        // the '..Applet started' Plug-In message)
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                }

                if (statusBarMessageShown) {
                    showStatus("Digital Signature Applet - " + DSAPPLET_VERSION);
                }

            }
        }).start();
    }

    /**
     * Sign a form using a certificate selected from a dialog box
     * @param formId the id of the form to lookup through DOM traversal
     * @return true if signing has completed successfully, false otherwise
     * @category JavaScript exposed method
     */
    public boolean sign(final String formId) {
        return sign(formId, null);
    }

    /**
     * Sign a form using the certificate with the supplied alias
     * @param formId the id of the form to lookup through DOM traversal
     * @param alias the alias of the selected certificate
     * @return true if signing has completed successfully, false otherwise
     * @category JavaScript exposed method
     * @since 2.1.0
     */
    public boolean sign(final String formId, final String alias) {
        class SignInternalRunnable implements Runnable {
            private boolean successful;

            public boolean isSuccessful() {
                return successful;
            }

            @Override
            public void run() {
                successful = signInternal(formId, alias);
            }
        }
        SignInternalRunnable sir = new SignInternalRunnable();

        available.acquireUninterruptibly();
        try {
            if (alias != null) {
                return signInternal(formId, alias);
            }

            // Null alias means the certificate selection dialog box will pop-up; 
            // hence, we need to be running in Swing's event dispatch thread
            if (!SwingUtilities.isEventDispatchThread()) {
                SwingUtilities.invokeAndWait(sir);
            } else {
                sir.run();
            }

            return sir.isSuccessful();
        } catch (Exception e) {
            logger.error("Internal sign failed", e);

            return false;
        } finally {
            available.release();
        }
    }

    /**
     * Sign the supplied plaintext using the certificate with the supplied alias
     * @param plaintext the plaintext to sign
     * @param alias the alias of the selected certificate
     * @return a jsonResponse containing the signature
     * @since 2.2.0
     * @category JavaScript exposed method
     */
    public String signPlaintext(final String plaintext, final String alias) {
        class SignPlaintextInternalRunnable implements Runnable {
            private String jsonResponse;

            public String getJsonResponse() {
                return jsonResponse;
            }

            @Override
            public void run() {
                jsonResponse = signPlaintextInternal(plaintext, alias);
            }
        }
        SignPlaintextInternalRunnable spir = new SignPlaintextInternalRunnable();

        available.acquireUninterruptibly();
        try {
            if (alias != null) {
                return signPlaintextInternal(plaintext, alias);
            }

            // Null alias means the certificate selection dialog box will pop-up; 
            // hence, we need to be running in Swing's event dispatch thread
            if (!SwingUtilities.isEventDispatchThread()) {
                SwingUtilities.invokeAndWait(spir);
            } else {
                spir.run();
            }

            return spir.getJsonResponse();
        } catch (Exception e) {
            logger.error("Internal sign plaintext failed", e);

            return "";
        } finally {
            available.release();
        }
    }

    /**
     * Check whether any eligible certificates are available 
     * @return true if eligible certificates are available; false otherwise
     * @category JavaScript exposed method
     */
    public boolean hasCertificates() {
        available.acquireUninterruptibly();
        try {
            KeyStoreProxy proxy = getKeyStoreProxy();

            return !createAliasX509CertificateChainPair(proxy).isEmpty();
        } catch (Exception e) {
            handleError("DSA0002", e);

            return false;
        } finally {
            available.release();
        }
    }

    /**
     * Retrieve a JSON expression of certificate aliases and their corresponding
     * descriptions
     * @return a JSON expression
     * @category JavaScript exposed method
     */
    public String getAliasedDescriptions() {
        available.acquireUninterruptibly();
        try {
            String json = "";
            KeyStoreProxy proxy = getKeyStoreProxy();

            Map<String, X509Certificate[]> aliasX509CertificateChainPair = createAliasX509CertificateChainPair(
                    proxy);
            CertificateTableModel ctm = new CertificateTableModel(aliasX509CertificateChainPair, messages);
            Environment.getSingleton().init(ctm);

            for (int i = 0; i < ctm.getRowCount(); i++) {
                X509Certificate certificate = ctm.getX509Certificate(i);
                if (new Date().compareTo(certificate.getNotAfter()) > 0) {
                    continue;
                }

                String alias = ctm.getAlias(i);

                // Column #0 is the name
                // Column #1 is the friendly name
                String name = (String) ctm.getValueAt(i, 0);
                if (name != null) {
                    name = StringEscapeUtils.escapeJavaScript(name);
                }
                String friendlyName = (String) ctm.getValueAt(i, 1);
                if (friendlyName != null) {
                    friendlyName = StringEscapeUtils.escapeJavaScript(friendlyName);
                }
                String compositeName = (friendlyName != null && friendlyName.length() > 0)
                        ? (friendlyName + " - " + name)
                        : name;

                String entry = "{ \"alias\": \"" + alias + "\", \"description\": \"" + compositeName + "\" }";

                if (json.length() > 0) {
                    json += ", ";
                }
                json += entry;
            }

            return "[ " + json + " ]";
        } catch (Exception e) {
            handleError("DSA0002", e);

            return "";
        } finally {
            available.release();
        }
    }

    private String getSelectedAlias(KeyStoreProxy proxy,
            Map<String, X509Certificate[]> aliasX509CertificateChainPair, Profiler profiler) {
        profiler.start("Keypair filtering");

        try {
            aliasX509CertificateChainPair = createAliasX509CertificateChainPair(proxy);
        } catch (Exception e) {
            handleError("DSA0002", e);
        }

        if (aliasX509CertificateChainPair.isEmpty()) {
            if (noCertificatesJSFunction != null) {
                LiveConnectProxy.getSingleton().eval(noCertificatesJSFunction + "();");

                return null;
            } else {
                logger.debug("noCertificatesJSFunction not set");
            }
        }

        profiler.start("User selection");

        CertificateTableModel ctm = new CertificateTableModel(aliasX509CertificateChainPair, messages);
        Environment.getSingleton().init(ctm);

        SelectCertificateDialog scd = new SelectCertificateDialog(ctm, expirationDateChecked, messages);

        scd.setModalityType(ModalityType.APPLICATION_MODAL);
        scd.setVisible(true);

        return scd.getSelectedAlias();
    }

    private boolean signInternal(String formId, String alias) {
        Profiler profiler = new Profiler("SIGNATURE");
        profiler.setLogger(logger);

        try { // Only for the purpose of stopping the Profiler on final 

            profiler.start("Keystore creation");

            KeyStoreProxy proxy = getKeyStoreProxy();

            Map<String, X509Certificate[]> aliasX509CertificateChainPair = new HashMap<String, X509Certificate[]>();

            if (alias == null) {
                alias = getSelectedAlias(proxy, aliasX509CertificateChainPair, profiler);
            }

            if (alias == null) {
                return false;
            }

            logger.debug("Selected alias: [{}]", alias);

            PrivateKey privateKey = null;
            try {
                privateKey = proxy.getPrivateKey(alias);
            } catch (Exception e) {
                handleError("DSA0003", e);
            }

            X509Certificate[] certificateChain = null;
            try {
                certificateChain = proxy.getX509CertificateChain(alias);
            } catch (Exception e) {
                handleError("DSA0004", e);
            }

            profiler.start("HTML form parsing");

            Strategy strategy = strategyFactory.getStrategy();

            FormParser parser = new FormParser(this, formId);
            parser.setContentHandler(strategy.getFormContentHandler());
            try {
                parser.parse();
            } catch (Exception e) {
                handleError("DSA0005", e);
            }

            profiler.start("Signature strategy");

            try {
                strategy.sign(privateKey, certificateChain);
            } catch (Exception e) {
                handleError("DSA0006", e);
            }

            if (successJSFunction != null) {
                LiveConnectProxy.getSingleton().eval(successJSFunction + "();");
            } else {
                logger.debug("successJSFunction not set");
            }

            return true;

        } finally {
            profiler.stop().log();
        }
    }

    private String signPlaintextInternal(final String plaintext, String alias) {
        Profiler profiler = new Profiler("PLAINTEXT SIGNATURE");
        profiler.setLogger(logger);

        try { // Only for the purpose of stopping the Profiler on final 

            profiler.start("Keystore creation");

            KeyStoreProxy proxy = getKeyStoreProxy();

            Map<String, X509Certificate[]> aliasX509CertificateChainPair = new HashMap<String, X509Certificate[]>();

            if (alias == null) {
                alias = getSelectedAlias(proxy, aliasX509CertificateChainPair, profiler);
            }

            if (alias == null) {
                return null;
            }

            logger.debug("Selected alias: [{}]", alias);

            PrivateKey privateKey = null;
            try {
                privateKey = proxy.getPrivateKey(alias);
            } catch (Exception e) {
                handleError("DSA0003", e);
            }

            X509Certificate[] certificateChain = null;
            try {
                certificateChain = proxy.getX509CertificateChain(alias);
            } catch (Exception e) {
                handleError("DSA0004", e);
            }

            Strategy strategy = strategyFactory.getStrategy();

            profiler.start("Signature strategy");

            String jsonResponse = null;
            try {
                jsonResponse = strategy.signPlaintext(plaintext, privateKey, certificateChain);
            } catch (Exception e) {
                handleError("DSA0006", e);
            }

            if (successJSFunction != null) {
                LiveConnectProxy.getSingleton().eval(successJSFunction + "();");
            } else {
                logger.debug("successJSFunction not set");
            }

            return jsonResponse;

        } finally {
            profiler.stop().log();
        }
    }

    private Map<String, X509Certificate[]> createAliasX509CertificateChainPair(KeyStoreProxy ksh)
            throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException {
        Map<String, X509Certificate[]> aliasX509CertificateChainPair = new HashMap<String, X509Certificate[]>();

        Set<String> aliases = ksh.aliases();
        for (String alias : aliases) {
            X509Certificate[] certificateChain = ksh.getX509CertificateChain(alias);
            if (certificateChain == null || certificateChain.length == 0) {
                logger.warn("Null certificate chain returned; alias=" + alias);

                continue;
            }

            X509Certificate certificate = certificateChain[0];

            String subjectName = certificate.getSubjectX500Principal().getName();
            String issuerName = certificate.getIssuerX500Principal().getName();
            BigInteger serialNumber = certificate.getSerialNumber();

            // Filter by subject

            if (getSubjectMatchingPattern() != null
                    && !getSubjectMatchingPattern().matcher(subjectName).matches()) {
                logger.info("Subject does not match; skipping" + ": certificate.subject=" + subjectName);
                continue;
            }

            // Filter by issuer

            if (getIssuerMatchingPattern() != null && !getIssuerMatchingPattern().matcher(issuerName).matches()) {
                logger.info("Issuer does not match; skipping" + ": certificate.subject=" + subjectName
                        + ", certificate.issuer=" + issuerName);
                continue;
            }

            // Filter by serial number

            if (getSerialNumbersAllowedSet() != null && !getSerialNumbersAllowedSet().contains(serialNumber)) {
                logger.info("Serial number is not allowed; skipping" + ": certificate.subject=" + subjectName
                        + ", certificate.serialNumber=" + serialNumber);
                continue;
            }

            // Filter by key usage

            if (keyUsageRestrictions != null
                    && !KeyUsageHelper.validateKeyUsage(certificate, keyUsageRestrictions)) {
                logger.info("Key usage restrictions not met; skipping" + ": certificate.subject=" + subjectName
                        + ", certificate.keyUsage=" + KeyUsageHelper.printKeyUsage(certificate));
                continue;
            }

            // Filter by private key

            if (!ksh.isKeyEntry(alias)) {
                logger.info("Private key not found; skipping" + ": certificate.subject=" + subjectName);
                continue;
            }

            logger.debug("Accepting certificate" + "; certificate.alias=" + alias + ", certificate.subject="
                    + subjectName + ", certificate.serialNumber=" + serialNumber);

            aliasX509CertificateChainPair.put(alias, ksh.getX509CertificateChain(alias));
        }

        return aliasX509CertificateChainPair;
    }

    @Override
    public String getAppletInfo() {
        return "Digital Signature Applet - " + DSAPPLET_VERSION + "\nCopyright \u00a9 2007-2013 Anestis Georgiadis"
                + "\nhttp://dsig.sourceforge.net";
    }

    private String printInfoMessage() {
        StringBuilder sb = new StringBuilder();
        sb.append(getAppletInfo());
        sb.append("\n\n");

        sb.append("Strategy: " + strategyFactory.getName() + "\n");

        return sb.toString();
    }

    private void handleError(String errorCode, Throwable cause) {
        ErrorDialog ed = new ErrorDialog(errorCode, cause, messages);
        ed.setModalityType(ModalityType.APPLICATION_MODAL);
        ed.setVisible(true);

        if (errorJSFunction != null) {
            LiveConnectProxy.getSingleton().eval(errorJSFunction + "(" + errorCode + ");");
        }

        if (cause instanceof RuntimeException) {
            throw (RuntimeException) cause;
        } else {
            throw new RuntimeException(cause);
        }
    }

}