it.trento.comune.j4sign.examples.GUITest.java Source code

Java tutorial

Introduction

Here is the source code for it.trento.comune.j4sign.examples.GUITest.java

Source

/**
 *   j4sign - an open, multi-platform digital signature solution
 *   Copyright (c) 2004 Roberto Resoli - Servizio Sistema Informativo - Comune di Trento.
 *
 *   This program is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU General Public License
 *   as published by the Free Software Foundation; either version 2
 *   of the License, or (at your option) any later version.
 *
 *   This program 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 General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */
/*
 * $Header: /cvsroot/j4sign/j4sign/src/java/core/it/trento/comune/j4sign/examples/GUITest.java,v 1.7 2011/04/15 08:23:02 resoli Exp $
 * $Revision: 1.7 $
 * $Date: 2011/04/15 08:23:02 $
 */
package it.trento.comune.j4sign.examples;

import iaik.pkcs.pkcs11.wrapper.PKCS11Constants;
import it.trento.comune.j4sign.cms.ExternalSignatureCMSSignedDataGenerator;
import it.trento.comune.j4sign.cms.ExternalSignatureSignerInfoGenerator;
import it.trento.comune.j4sign.pcsc.CardInfo;
import it.trento.comune.j4sign.pcsc.PCSCHelper;

import java.awt.*;

import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;

import org.bouncycastle.asn1.DERObjectIdentifier;
import org.bouncycastle.asn1.DEROutputStream;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.DigestInfo;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.CMSProcessable;
import org.bouncycastle.cms.CMSProcessableByteArray;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.CMSSignedDataGenerator;
import org.bouncycastle.cms.SignerInformation;
import org.bouncycastle.cms.SignerInformationStore;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import java.io.*;
import java.security.*;
import java.security.cert.CertStore;
import java.security.cert.CertStoreException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.CollectionCertStoreParameters;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;

/**
 * A graphical user interface program for testing the generation of a CMS signed
 * message, using a pkcs11 token (usually a SmartCard). This examples shows a
 * simple method for signing text files; other file types are explicitly
 * excluded because we want avoid to manage here complex issues related to file
 * visualization. <br>
 * The italian law states clearly that the signing procedure has to make aware
 * the signer of the content being signed. Signing files that contains macros or
 * other procedures that can dinamically modify the visualization of the content
 * is also explicitely prohibithed. <br>
 * As a good rule, we should sign only a content we know and comprehend
 * completely. <br>
 * For this reason, we also STRONGLY SUGGEST NOT TO SIGN proprietary file
 * formats. <br>
 * Threads and timers are used in this example are used to enhance user
 * interface, permitting live logging of signing procedure and the use of a
 * progress bar.
 * <p>
 * Multiple signatures are permitted, each with different token types; the
 * generated CMS message keeps signers informations at the same level (similar
 * to a paper document with multiple signatures). I call this arrangement
 * "combined signatures", in contrast with "nested signatures" (like a signed
 * paper document put in a signed envelope).
 * <p>
 * <b>N.B. note that in this example signature verification only ensures signed
 * data integrity; a complete verification to ensure non-repudiation requires
 * checking the full certification path including the CA root certificate, and
 * CRL verification on the CA side. <br>
 * (Good stuff for a next release ...) </b>
 * 
 * @author Roberto Resoli
 */
public class GUITest extends JFrame implements java.awt.event.ActionListener, DocumentListener {
    private JTextArea logArea = null;

    private JTextArea dataArea = null;

    private JPasswordField pwd = null;

    private JTextArea certArea = null;

    private DigestSignTask signTask = null;

    private Timer signTimer = null;

    private FindCertTask certTask = null;

    private Timer findTimer = null;

    private JButton f = null;

    private JButton c = null;

    private JButton s = null;

    private JProgressBar progressBar = null;

    boolean debug = false;

    boolean submitAfterSigning = false;

    private byte[] bytesToSign = null;

    private String encodedDigest = null;

    private byte[] encryptedDigest;

    private java.io.PrintStream log = null;

    public final static int ONE_SECOND = 1000;

    private java.lang.String cryptokiLib = null;

    private java.lang.String signerLabel = null;

    private byte[] certificate = null;

    private CMSProcessable msg = null;

    private ExternalSignatureCMSSignedDataGenerator cmsGenerator = null;

    private ExternalSignatureSignerInfoGenerator signerInfoGenerator = null;

    private ArrayList signersCertList = null;

    private File fileToSign = null;

    private boolean forcingCryptoki = false;

    private static String PROPERTIES_FILE = "clitest.properties";

    private boolean makeDigestOnToken = false;

    private String digestAlg = CMSSignedDataGenerator.DIGEST_SHA256;

    private String encAlg = CMSSignedDataGenerator.ENCRYPTION_RSA;

    /**
     * @return Returns the forcingCryptoki.
     */
    public boolean isForcingCryptoki() {
        return forcingCryptoki;
    }

    private void loadProperties() {
        Properties props = new Properties();

        String propertiesFile = PROPERTIES_FILE;

        System.out.println("Trying to load properties from: '" + propertiesFile + "'");
        try {
            InputStream in = getClass().getResourceAsStream("/" + propertiesFile);
            if (in != null) {
                props.load(in);
                in.close();
            } else
                System.out.println("'" + propertiesFile + "' not found!");
        } catch (IOException e) {
            System.out.println(e);
        }

        if (props.size() > 0) {
            Iterator i = props.entrySet().iterator();
            System.out.println("loaded properties:");
            while (i.hasNext()) {
                Map.Entry me = (Map.Entry) i.next();
                System.out.println((me.getKey().toString() + ": " + me.getValue()));
            }

            if (props.getProperty("digest.algorithm") != null)
                this.digestAlg = props.getProperty("digest.algorithm");

            if (props.getProperty("digest.ontoken") != null)
                this.makeDigestOnToken = Boolean.valueOf(props.getProperty("digest.ontoken")).booleanValue();

            if (props.getProperty("encryption.algorithm") != null)
                this.encAlg = props.getProperty("encryption.algorithm");
        }

    }

    /**
     * The class constructor.
     * 
     * @param title
     *            , shown on the window title bar.
     * @param aDebug
     *            if True, causes operation log to be shown in the GUI, in a
     *            dedicated pane.
     * @throws java.awt.HeadlessException
     */
    public GUITest(String title, String aDebug, String aCryptoki) {
        super(title);

        System.out.println("Initializing GUITest ...");

        System.out.println("Loading properties ...");
        loadProperties();

        if (aDebug != null)
            this.debug = Boolean.valueOf(aDebug).booleanValue();

        if (aCryptoki != null) {
            this.cryptokiLib = aCryptoki;
            this.forcingCryptoki = true;
        }

        System.out.println("\nUsing cryptoki:\t" + getCryptokiLib());
        System.out.println("Using signer:\t" + getSignerLabel() + "\n");

        getContentPane().setLayout(new BorderLayout());

        if (!debug)
            log = System.out;
        else {
            logArea = new JTextArea();
            logArea.setFont(new Font("Monospaced", Font.PLAIN, 12));

            log = new PrintStream(new JTextAreaOutputStream(logArea), true);

            dataArea = new JTextArea();

            JPanel dataAreaPanel = new JPanel();
            dataAreaPanel.setLayout(new BorderLayout());
            dataAreaPanel.add(dataArea, BorderLayout.CENTER);
            dataAreaPanel.add(new JLabel("Put down here the text to sign:"), BorderLayout.NORTH);

            dataArea.getDocument().addDocumentListener(this);
            //dataArea.getDocument().putProperty("data", "Text Area");

            f = new JButton("Load File");
            f.setEnabled(true);

            c = new JButton("Save Certificate");
            c.setEnabled(false);

            s = new JButton("Save Signed File");
            s.setEnabled(false);

            f.addActionListener(this);
            c.addActionListener(this);
            s.addActionListener(this);

            JScrollPane logScrollPane = new JScrollPane(logArea);
            JScrollPane dataScrollPane = new JScrollPane(dataAreaPanel);

            JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, logScrollPane, dataScrollPane);
            splitPane.setOneTouchExpandable(true);

            splitPane.setDividerLocation(200);
            // splitPane.setDividerLocation(0);

            // Provide minimum sizes for the two components in the split pane
            /*
             * Dimension minimumSize = new Dimension(100, 50);
             * logScrollPane.setMinimumSize(minimumSize);
             * dataScrollPane.setMinimumSize(minimumSize);
             */
            // Provide a preferred size for the split pane
            splitPane.setPreferredSize(new Dimension(600, 400));

            getContentPane().add(splitPane, BorderLayout.CENTER);
        }

        pwd = new JPasswordField();
        pwd.setPreferredSize(new Dimension(100, 25));
        pwd.addActionListener(this);

        JPanel southPanel = new JPanel();
        southPanel.setLayout(new BoxLayout(southPanel, BoxLayout.Y_AXIS));
        JPanel controlsPanel = new JPanel();
        JPanel statusPanel = new JPanel();

        JPanel certPanel = new JPanel();
        certPanel.setLayout(new BoxLayout(certPanel, BoxLayout.X_AXIS));

        certArea = new JTextArea();
        certArea.setPreferredSize(new Dimension(100, 40));
        certArea.setEditable(false);
        certArea.setLineWrap(true);
        certArea.setFont(new Font("Sans-serif", Font.BOLD, 12));

        certPanel.add(certArea);

        statusPanel.setLayout(new BoxLayout(statusPanel, BoxLayout.X_AXIS));

        controlsPanel.add(pwd);

        if (debug) {
            controlsPanel.add(f);
            controlsPanel.add(s);
            controlsPanel.add(c);
        }

        progressBar = new JProgressBar();
        progressBar.setStringPainted(false);
        progressBar.setStringPainted(true);

        // setStatus(DigestSignTask.RESET,
        // "Inserire il pin e battere INVIO per firmare.");

        statusPanel.add(progressBar);

        southPanel.add(controlsPanel);
        southPanel.add(certPanel);
        southPanel.add(statusPanel);

        getContentPane().add(southPanel, debug ? BorderLayout.SOUTH : BorderLayout.CENTER);

        enableControls(false);

        findCert();

    }

    private void findCert() {

        long mechanism = algToMechanism(this.makeDigestOnToken, this.digestAlg, this.encAlg);

        if (mechanism == -1L)
            setStatus(ERROR, "Impossibile determinare il meccanismo!");
        else {

            // find certificate action
            initStatus(0, FindCertTask.FIND_MAXIMUM);

            // Create a new sign task.
            certTask = new FindCertTask(getCryptokiLib(), getSignerLabel(), log);
            // Create a timer.
            // NOTE: we define an action listener on the fly while
            // passing
            // an instance of it to the Timer constructor.
            findTimer = new Timer(ONE_SECOND, new java.awt.event.ActionListener() {

                public void actionPerformed(java.awt.event.ActionEvent evt) {

                    setStatus(certTask.getCurrent(), certTask.getMessage());

                    if (!certTask.isTokenPresent()) {
                        progressBar.setIndeterminate(true);
                        enableControls(false);
                    } else {
                        progressBar.setIndeterminate(false);
                    }
                    if (certTask.done()) {
                        findTimer.stop();
                        progressBar.setValue(progressBar.getMinimum());
                        if (certTask.getCurrent() == FindCertTask.FIND_DONE) {
                            Toolkit.getDefaultToolkit().beep();

                            setCertificate(certTask.getCertificate());

                            try {
                                certArea.setText(getJavaCertificate().getSubjectDN().toString());
                            } catch (CertificateException e) {
                                log.println("Error getting certificate Subject DN");
                            }

                        }

                        if (!"".equals(dataArea.getText()))
                            enableControls(true);
                    }
                }// end of actionPerformed definition
            });// end of ActionListener definition and Timer
            // constructor call.
        }

        certTask.setMechanism(mechanism);
        certTask.go();
        findTimer.start();
    }

    public static void main(String[] args) {

        Security.insertProviderAt(new BouncyCastleProvider(), 3);

        String cryptoki = (args.length == 1) ? args[0] : null;

        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (UnsupportedLookAndFeelException ex) {
        } catch (IllegalAccessException ex) {
        } catch (InstantiationException ex) {
        } catch (ClassNotFoundException ex) {
        }

        GUITest frame = new GUITest("j4sign GUI Test", "true", cryptoki);

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        frame.pack();

        Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
        frame.setLocation((d.width - frame.getWidth()) / 2, (d.height - frame.getHeight()) / 2);

        frame.setVisible(true);

    }

    /**
     * Prepares a signing procedure.
     * 
     * @param digestAlg
     * @param encryptionAlg
     * @param digestOnToken
     * @throws InvalidKeyException
     * @throws SignatureException
     * @throws NoSuchProviderException
     * @throws NoSuchAlgorithmException
     * @throws IOException
     * @throws CMSException
     * @throws CertificateException
     */
    private void openSignature(String digestAlg, String encryptionAlg, boolean digestOnToken)
            throws InvalidKeyException, SignatureException, NoSuchProviderException, NoSuchAlgorithmException,
            IOException, CMSException, CertificateException {

        this.msg = new CMSProcessableByteArray(dataArea.getText().getBytes("UTF8"));

        this.cmsGenerator = new ExternalSignatureCMSSignedDataGenerator();

        this.signersCertList = new ArrayList();

        log.println("Certificate bytes:\n" + formatAsHexString(getCertificate()));

        java.security.cert.X509Certificate javaCert = getJavaCertificate();

        this.signerInfoGenerator = new ExternalSignatureSignerInfoGenerator(digestAlg, encryptionAlg);
        this.signerInfoGenerator.setCertificate(javaCert);

        this.signersCertList.add(javaCert);

        this.bytesToSign = this.signerInfoGenerator.getBytesToSign(PKCSObjectIdentifiers.data, msg, "BC");

        if (!digestOnToken) {
            log.println("\nCalculating digest ...\n");

            MessageDigest md = MessageDigest.getInstance(digestAlg);
            md.update(bytesToSign);
            byte[] rawDigest = md.digest();

            log.println("Encapsulating in a DigestInfo...");

            byte[] dInfoBytes = encapsulateInDigestInfo(digestAlg, rawDigest);

            log.println("Adding Pkcs1 padding...");

            byte[] paddedBytes = applyPkcs1Padding(128, dInfoBytes);

            log.println("Encapsulated digest:\n" + formatAsHexString(dInfoBytes));
            log.println("Done.");
            setEncodedDigest(encodeFromBytes(dInfoBytes));
        }

    }

    /**
     * Starts a signing task in a separate thread.
     * 
     * @param digestOnToken
     *            if true, the cryptoki - card takes care of digesting; raw
     *            bytes to sign are passed to cryptoki functions.
     */
    private void sign() {

        initStatus(0, DigestSignTask.SIGN_MAXIMUM);

        // Create a new sign task.
        signTask = new DigestSignTask(getCryptokiLib(), getSignerLabel(), log);

        // Create a timer.
        // NOTE: we define an action listener on the fly while
        // passing
        // an instance of it to the Timer constructor.
        signTimer = new Timer(ONE_SECOND, new java.awt.event.ActionListener() {

            public void actionPerformed(java.awt.event.ActionEvent evt) {
                setStatus(signTask.getCurrent(), signTask.getMessage());
                if (signTask.done()) {
                    signTimer.stop();
                    progressBar.setValue(progressBar.getMinimum());
                    if (signTask.getCurrent() == DigestSignTask.SIGN_DONE) {
                        Toolkit.getDefaultToolkit().beep();

                        setEncryptedDigest(signTask.getEncryptedDigest());
                        setCertificate(signTask.getCertificate());

                        try {
                            closeSignature();
                        } catch (CertificateException e) {
                            log.println("Error closing signature process:\n" + e);
                        }

                    }
                    enableControls(true);
                }
            }// end of actionPerformed definition
        });// end of ActionListener definition and Timer
           // constructor call.

        if (!this.makeDigestOnToken && getEncodedDigest() == null)
            setStatus(ERROR, "Digest non impostato");
        else {
            enableControls(false);
            if (!this.makeDigestOnToken)
                signTask.setDigest(decodeToBytes(getEncodedDigest()));
            else
                signTask.setDataStream(new ByteArrayInputStream(this.bytesToSign));

            long mechanism = algToMechanism(this.makeDigestOnToken, this.digestAlg, this.encAlg);

            if (mechanism == -1L)
                setStatus(ERROR, "Impossibile determinare il meccanismo!");
            else {
                signTask.setMechanism(mechanism);
                signTask.setPassword(pwd.getPassword());
                signTask.go();
                signTimer.start();
            }
        }
    }

    /**
     * Terminates the signing procedure creating the signer information data
     * structure.
     * 
     * @throws CertificateException
     */
    private void closeSignature() throws CertificateException {
        if ((getCertificate() != null) && (getEncryptedDigest() != null)) {

            log.println("======== Encryption completed =========");
            log.println("Encrypted Digest bytes:\n" + formatAsHexString(getEncryptedDigest()));

            this.signerInfoGenerator.setSignedBytes(getEncryptedDigest());

            this.cmsGenerator.addSignerInf(this.signerInfoGenerator);

            s.setEnabled(true);
            c.setEnabled(true);

        }
    }

    /**
     * Creates the signed data structure, using signer infos precedently
     * accumulated.
     * 
     * @return
     * @throws CertStoreException
     * @throws InvalidAlgorithmParameterException
     * @throws CertificateExpiredException
     * @throws CertificateNotYetValidException
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws CMSException
     */
    private CMSSignedData buildCMSSignedData()
            throws CertStoreException, InvalidAlgorithmParameterException, CertificateExpiredException,
            CertificateNotYetValidException, NoSuchAlgorithmException, NoSuchProviderException, CMSException {
        CMSSignedData s = null;

        if (this.signersCertList.size() != 0) {

            // Per passare i certificati al generatore li si incapsula
            // in un
            // CertStore.
            CertStore store = CertStore.getInstance("Collection",
                    new CollectionCertStoreParameters(this.signersCertList), "BC");

            log.println("Adding certificates ... ");
            this.cmsGenerator.addCertificatesAndCRLs(store);

            // Finalmente, si pu creare il l'oggetto CMS.
            log.println("Generating CMSSignedData ");
            s = this.cmsGenerator.generate(this.msg, true);

            // Verifica

            log.println("\nStarting CMSSignedData verification ... ");
            // recupero dal CMS la lista dei certificati
            CertStore certs = s.getCertificatesAndCRLs("Collection", "BC");

            // Recupero i firmatari.
            SignerInformationStore signers = s.getSignerInfos();
            Collection c = signers.getSigners();

            log.println(c.size() + " signers found.");

            Iterator it = c.iterator();

            // ciclo tra tutti i firmatari
            int i = 0;
            while (it.hasNext()) {
                SignerInformation signer = (SignerInformation) it.next();
                Collection certCollection = certs.getCertificates(signer.getSID());

                if (certCollection.size() == 1) {
                    // Iterator certIt = certCollection.iterator();
                    // X509Certificate cert = (X509Certificate)
                    // certIt.next();

                    X509Certificate cert = (X509Certificate) certCollection.toArray()[0];
                    log.println(i + ") Verifiying signature from:\n" + cert.getSubjectDN());
                    /*
                     * log.println("Certificate follows:");
                     * log.println("====================================");
                     * log.println(cert);
                     * log.println("====================================");
                     */
                    if (signer.verify(cert, "BC")) {

                        log.println("SIGNATURE " + i + " OK!");
                    } else
                        log.println("SIGNATURE " + i + " Failure!");
                } else
                    log.println("There is not exactly one certificate for this signer!");
                i++;
            }
        }

        return s;
    }

    /**
     * The "control center" of the class, mandatory to satisfy the
     * java.awt.event.ActionListener interface contract.
     * 
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    public void actionPerformed(java.awt.event.ActionEvent e) {
        try {

            // sign action
            if (e.getSource() == pwd) {
                if ("".equals(dataArea.getText()))
                    return;
                // disable text area modification.
                this.f.setEnabled(false);
                this.dataArea.setEditable(false);

                if (detectCardAndCriptoki()) {

                    openSignature(CMSSignedDataGenerator.DIGEST_SHA256, CMSSignedDataGenerator.ENCRYPTION_RSA,
                            this.makeDigestOnToken);
                    // this launches the signing thread (see task above)
                    sign();

                } // end of if( detect...

            }

            if (e.getSource() == f) {

                log.println("Loading file...");

                String filePath = System.getProperty("user.home") + System.getProperty("file.separator");

                JFileChooser fc = new JFileChooser(new File(filePath));

                // Show dialog; this method does not return until dialog is
                // closed
                if (fc.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {

                    // Get the selected file
                    File file = fc.getSelectedFile();

                    String typeDesc = fc.getTypeDescription(file);

                    try {
                        if (isTextFile(file)) {
                            FileInputStream fis = new FileInputStream(file);

                            ByteArrayOutputStream baos = new ByteArrayOutputStream();
                            byte[] buffer = new byte[1024];
                            int bytesRead = -1;

                            while ((bytesRead = fis.read(buffer, 0, buffer.length)) >= 0) {
                                baos.write(buffer, 0, bytesRead);
                            }

                            fis.close();
                            log.println("File: '" + file.getAbsolutePath() + "' loaded.");

                            dataArea.setText(baos.toString());

                            this.setFileToSign(file);

                            if (!"".equals(dataArea.getText()) && getCertificate() != null)
                                pwd.setEnabled(true);
                            else
                                pwd.setEnabled(false);

                        } else {
                            JOptionPane.showMessageDialog(null, "This does not appears as a text file!",
                                    "Error loading file.", JOptionPane.ERROR_MESSAGE);
                            log.println("This does not appears as a text file!");
                        }
                    } catch (IOException ioe) {
                        System.err.println(ioe);
                    }

                }
            }

            if (e.getSource() == c) {
                log.println("Saving signer certificate");
                String filePath = System.getProperty("user.home") + System.getProperty("file.separator");

                JFileChooser fc = new JFileChooser(new File(filePath));

                // Show dialog; this method does not return until dialog is
                // closed
                fc.showSaveDialog(this);

                // Get the selected file
                File file = fc.getSelectedFile();

                FileOutputStream fos = new FileOutputStream(file);
                fos.write(getCertificate());
                fos.flush();
                fos.close();

                log.println("Signer certificate saved to: " + file.getAbsolutePath());
            }

            if (e.getSource() == s) {

                log.println("Building  CMSSignedData...");

                CMSSignedData cms = buildCMSSignedData();

                log.println("Saving signed message");

                String dirPath = System.getProperty("user.home");
                if (this.getFileToSign() != null) {
                    dirPath = this.getFileToSign().getParent();
                }

                dirPath = dirPath + System.getProperty("file.separator");

                JFileChooser fc = new JFileChooser(new File(dirPath));

                String p7mFilePath = (this.getFileToSign() != null)
                        ? this.getFileToSign().getAbsolutePath() + ".p7m"
                        : dirPath + "guitest.txt.p7m";

                fc.setSelectedFile(new File(p7mFilePath));

                // Show dialog; this method does not return until dialog is
                // closed
                fc.showSaveDialog(this);

                // Get the selected file
                File file = fc.getSelectedFile();

                FileOutputStream fos = new FileOutputStream(file);
                fos.write(cms.getEncoded());
                fos.flush();
                fos.close();

                log.println("Signed message saved to: " + file.getAbsolutePath());
            }

        } catch (Exception ex) {
            log.println(ex.toString());

        } finally {
            pwd.setText("");
        }
    }

    /**
     * Tests if a file is a text file; this method probably works only in a
     * unicode system (fixme).
     * 
     * @param f
     * @return
     * @throws IOException
     */
    private boolean isTextFile(File f) throws IOException {
        // Used for its canDisplay(char) method;
        Font testFont = new Font("Courier", Font.PLAIN, 10);
        FileReader fr = new FileReader(f.getAbsolutePath());

        int charRead = 0;
        boolean isText = true;
        while (((charRead = fr.read()) != -1) && isText)
            isText = Character.isISOControl((char) charRead) || testFont.canDisplay((char) charRead);
        fr.close();

        return isText;
    }

    /**
     * Takes text from data area and digests it.
     * 
     * @throws NoSuchAlgorithmException
     * @throws UnsupportedEncodingException
     */
    private void prepareDigestFromTextArea() throws NoSuchAlgorithmException, UnsupportedEncodingException {
        log.println("\nCalculating digest ...\n");
        java.security.MessageDigest md5 = java.security.MessageDigest.getInstance("MD5");
        md5.update(dataArea.getText().getBytes("UTF8"));
        byte[] digest = md5.digest();
        log.println("digest:\n" + formatAsHexString(digest));
        log.println("Done.");
        setEncodedDigest(encodeFromBytes(digest));
    }

    /**
     * Decodes a base64 String in a normal Unicode string.
     * 
     * @param s
     * @return
     */
    public String decode(String s) {
        try {
            byte[] bytes = decodeToBytes(s);
            if (bytes != null)
                return new String(bytes, "UTF8");
        } catch (java.io.UnsupportedEncodingException e) {
            log.println("Errore di encoding: " + e);
        }
        return null;
    }

    /**
     * Converts a base64 String in a byte array.
     * 
     * @param s
     * @return
     */
    public byte[] decodeToBytes(String s) {
        byte[] stringBytes = null;
        try {
            sun.misc.BASE64Decoder decoder = new sun.misc.BASE64Decoder();
            stringBytes = decoder.decodeBuffer(s);
        } catch (java.io.IOException e) {
            log.println("Errore di io: " + e);
        }
        return stringBytes;
    }

    /**
     * Enables GUI controls.
     * 
     * @param enable
     *            boolean
     */

    private void enableControls(boolean enable) {

        pwd.setEnabled(enable);

    }

    /**
     * Encodes a String into its base64 encoding version.
     * 
     * @param s
     * @return
     */
    public String encode(String s) {
        try {
            return encodeFromBytes(s.getBytes("UTF8"));
        } catch (java.io.UnsupportedEncodingException e) {
            log.println("Errore di encoding: " + e);
        }
        return null;
    }

    /**
     * Creates the base64 encoding of a byte array.
     * 
     * @param bytes
     * @return
     */
    public String encodeFromBytes(byte[] bytes) {
        String encString = null;

        sun.misc.BASE64Encoder encoder = new sun.misc.BASE64Encoder();
        encString = encoder.encode(bytes);

        return encString;
    }

    /**
     * Returns information about this applet.
     * 
     * @return a string of information about this applet
     */
    public String getAppletInfo() {
        return "SignApplet\n" + "\n" + "This type was created in VisualAge.\n" + "";
    }

    /**
     * Returns the signer's certificate.
     * 
     * @return byte
     */
    public byte[] getCertificate() {
        return certificate;
    }

    public java.security.cert.X509Certificate getJavaCertificate() throws CertificateException {

        // get Certificate
        java.security.cert.CertificateFactory cf = java.security.cert.CertificateFactory.getInstance("X.509");
        java.io.ByteArrayInputStream bais = new java.io.ByteArrayInputStream(getCertificate());
        java.security.cert.X509Certificate javaCert = (java.security.cert.X509Certificate) cf
                .generateCertificate(bais);

        return javaCert;
    }

    /**
     * Returns the cryptoki library name.
     * 
     * @return java.lang.String
     */
    private java.lang.String getCryptokiLib() {
        return cryptokiLib;
    }

    /**
     * Returns the base64 encoding of the digest.
     * 
     * @return the base64 encoding.
     */
    public String getEncodedDigest() {

        return this.encodedDigest;
    }

    /**
     * Gets the digest encrypted with the private key of the signer.
     * 
     * @return
     */
    public byte[] getEncryptedDigest() {
        return encryptedDigest;
    }

    /**
     * Returns the label identifiyng the signer objects on the token.
     * 
     * @return
     */
    private java.lang.String getSignerLabel() {
        return signerLabel;
    }

    /**
     * Resets the progress bar status.
     * 
     * @param min
     * @param max
     */
    private void initStatus(int min, int max) {
        progressBar.setMinimum(min);
        progressBar.setMaximum(max);
        setStatus(min, "");
    }

    /**
     * Tests if the program is in debug mode
     * 
     * @return boolean
     */
    private boolean isDebugMode() {
        return debug;
    }

    /**
     * Sets the signer certificate
     * 
     * @param newCertificate
     */
    private void setCertificate(byte[] newCertificate) {
        certificate = newCertificate;
    }

    /**
     * Sets the cryptoki library name.
     * 
     * @param newCryptokiLib
     */
    private void setCryptokiLib(java.lang.String newCryptokiLib) {
        cryptokiLib = newCryptokiLib;
    }

    /**
     * Sets the base64 encoded digest.
     * 
     * @param data
     */
    public void setEncodedDigest(String data) {
        this.encodedDigest = data;
    }

    /**
     * Sets the private-key encrypted digest
     * 
     * @param newEncryptedDigest
     */
    public void setEncryptedDigest(byte[] newEncryptedDigest) {
        encryptedDigest = newEncryptedDigest;
    }

    /**
     * Sets the label identifiyng the signer objects on the token.
     * 
     * @param newSignerLabel
     */
    private void setSignerLabel(java.lang.String newSignerLabel) {
        signerLabel = newSignerLabel;
    }

    /**
     * Sets the current status of the program (shown in the progress bar and
     * with alerts in case of error.
     * 
     * @param code
     * @param statusString
     */
    private void setStatus(int code, String statusString) {
        if (code == DigestSignTask.ERROR) {
            pwd.setText("");
            Toolkit.getDefaultToolkit().beep();
            JOptionPane.showMessageDialog(null, statusString, "Errore!", JOptionPane.ERROR_MESSAGE);
            code = 0;
            statusString = "";
        }
        progressBar.setValue(code);
        progressBar.setString(statusString);
    }

    private long algToMechanism(boolean digestOnToken, String digestAlg, String encryptionAlg) {

        long mechanism = -1L;

        if (CMSSignedDataGenerator.ENCRYPTION_RSA.equals(encryptionAlg))
            if (digestOnToken) {
                if (CMSSignedDataGenerator.DIGEST_MD5.equals(digestAlg))
                    mechanism = PKCS11Constants.CKM_MD5_RSA_PKCS;
                else if (CMSSignedDataGenerator.DIGEST_SHA1.equals(digestAlg))
                    mechanism = PKCS11Constants.CKM_SHA1_RSA_PKCS;
                else if (CMSSignedDataGenerator.DIGEST_SHA256.equals(digestAlg))
                    mechanism = PKCS11Constants.CKM_SHA256_RSA_PKCS;
            } else
                mechanism = PKCS11Constants.CKM_RSA_PKCS;

        return mechanism;
    }

    /**
     * Converts a byte array in its exadecimal representation.
     * 
     * @param bytes
     * @return
     */
    String formatAsHexString(byte[] bytes) {
        int n, x;
        String w = new String();
        String s = new String();
        for (n = 0; n < bytes.length; n++) {

            x = (int) (0x000000FF & bytes[n]);
            w = Integer.toHexString(x).toUpperCase();
            if (w.length() == 1)
                w = "0" + w;
            s = s + w + ((n + 1) % 16 == 0 ? "\n" : " ");
        }
        return s;
    }

    /**
     * This triggers the PCSC wrapper stuff; a {@link PCSCHelper}class is used
     * to detect reader and token presence, trying also to provide a candidate
     * PKCS#11 cryptoki for it.
     * 
     * @return true if a token with corresponding candidate cryptoki was
     *         detected.
     * @throws IOException
     */
    private boolean detectCardAndCriptoki() throws IOException {
        CardInfo ci = null;
        boolean cardPresent = false;

        PCSCHelper pcsc = new PCSCHelper(true);
        java.util.List cards = pcsc.findCards();
        cardPresent = !cards.isEmpty();
        if (!isForcingCryptoki()) {
            log.println("\n\n========= DETECTING CARD ===========");
            log.println("Trying to detect card via PCSC ...");
            log.println("Resetting cryptoki name");
            setCryptokiLib(null);
            if (cardPresent) {
                ci = (CardInfo) cards.get(0);
                setCryptokiLib(ci.getProperty("lib"));

                log.println("\n\nFor signing we will use card: '" + ci.getProperty("description")
                        + "' with criptoki '" + ci.getProperty("lib") + "'");

            } else
                log.println("Sorry, no card detected!");
        } else
            System.out.println("\n\nFor signing we are forcing use of cryptoki: '" + getCryptokiLib() + "'");

        log.println("=================================");

        return (getCryptokiLib() != null);
    }

    public File getFileToSign() {
        return fileToSign;
    }

    public void setFileToSign(File fileToSign) {
        this.fileToSign = fileToSign;
    }

    private byte[] applyPkcs1Padding(int resultLength, byte[] srcBytes) {

        int paddingLength = resultLength - srcBytes.length;

        byte[] dstBytes = new byte[resultLength];

        dstBytes[0] = 0x00;
        dstBytes[1] = 0x01;
        for (int i = 2; i < (paddingLength - 1); i++) {
            dstBytes[i] = (byte) 0xFF;
        }
        dstBytes[paddingLength - 1] = 0x00;
        for (int i = 0; i < srcBytes.length; i++) {
            dstBytes[paddingLength + i] = srcBytes[i];
        }
        return dstBytes;
    }

    private byte[] encapsulateInDigestInfo(String digestAlg, byte[] digestBytes) throws IOException {

        byte[] bcDigestInfoBytes = null;
        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
        DEROutputStream dOut = new DEROutputStream(bOut);

        DERObjectIdentifier digestObjId = new DERObjectIdentifier(digestAlg);
        AlgorithmIdentifier algId = new AlgorithmIdentifier(digestObjId, null);
        DigestInfo dInfo = new DigestInfo(algId, digestBytes);

        dOut.writeObject(dInfo);
        return bOut.toByteArray();

    }

    public void changedUpdate(DocumentEvent e) {
        // never fired for a plain text document
    }

    public void insertUpdate(DocumentEvent e) {
        // dataArea action
        if (!"".equals(dataArea.getText()) && getCertificate() != null)
            pwd.setEnabled(true);
        else
            pwd.setEnabled(false);

    }

    public void removeUpdate(DocumentEvent e) {
        // dataArea action
        if (!"".equals(dataArea.getText()) && getCertificate() != null)
            pwd.setEnabled(true);
        else
            pwd.setEnabled(false);

    }

}