org.votingsystem.signature.dnie.DNIePDFContentSigner.java Source code

Java tutorial

Introduction

Here is the source code for org.votingsystem.signature.dnie.DNIePDFContentSigner.java

Source

package org.votingsystem.signature.dnie;

import iaik.pkcs.pkcs11.*;
import iaik.pkcs.pkcs11.objects.Attribute;
import iaik.pkcs.pkcs11.objects.*;
import iaik.pkcs.pkcs11.objects.Object;
import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.cms.*;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.cms.*;
import org.votingsystem.model.UserVS;
import org.votingsystem.signature.util.CMSUtils;
import org.votingsystem.signature.util.CertUtils;
import org.votingsystem.signature.util.ContentSignerVS;
import org.votingsystem.throwable.ExceptionVS;
import org.votingsystem.util.ContextVS;
import org.votingsystem.util.FileUtils;
import org.votingsystem.util.OSValidator;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.security.MessageDigest;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.cert.CertStore;
import java.security.cert.Certificate;
import java.security.cert.CollectionCertStoreParameters;
import java.security.cert.X509Certificate;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

import static org.votingsystem.util.ContextVS.*;

/**
* License: https://github.com/votingsystem/votingsystem/wiki/Licencia
*/
public class DNIePDFContentSigner extends CMSSignedGenerator implements ContentSignerVS {

    private static Logger log = Logger.getLogger(DNIePDFContentSigner.class.getSimpleName());

    private static final int BUFFER_SIZE = 4096;

    private static Mechanism instanceSignatureMechanism;

    private Session pkcs11Session;
    private Module pkcs11Module;
    private Token token;
    private RSAPrivateKey signatureKey;
    private X509Certificate certUser = null;
    private X509Certificate certIntermediate = null;
    private X509Certificate certCA = null;

    private class SignerInf {

        private final SignerIdentifier signerIdentifier;
        private final String digestOID = PDF_DIGEST_OID;
        private final String encOID = ENCRYPTION_RSA;
        private final CMSAttributeTableGenerator sAttr;
        private final CMSAttributeTableGenerator unsAttr;
        private final AttributeTable baseSignedTable;
        private Session pkcs11Session;

        SignerInf(SignerIdentifier signerIdentifier, Session pkcs11Session, CMSAttributeTableGenerator unsAttr) {
            this.pkcs11Session = pkcs11Session;
            this.signerIdentifier = signerIdentifier;
            this.sAttr = new DefaultSignedAttributeTableGenerator();
            this.unsAttr = unsAttr;
            this.baseSignedTable = null;
        }

        AlgorithmIdentifier getDigestAlgorithmID() {
            return new AlgorithmIdentifier(new DERObjectIdentifier(digestOID), new DERNull());
        }

        SignerInfo toSignerInfo(DERObjectIdentifier contentType, CMSProcessable content, SecureRandom random,
                Provider sigProvider, boolean addDefaultAttributes, boolean isCounterSignature) throws Exception {

            AlgorithmIdentifier digAlgId = getDigestAlgorithmID();
            String digestName = CMSUtils.getDigestId(digestOID);
            String signatureName = digestName + "with" + CMSUtils.getEncryptiontId(encOID);
            AlgorithmIdentifier encAlgId = getEncAlgorithmIdentifier(encOID, null);

            ByteArrayOutputStream out = null;
            if (content != null) {
                out = new ByteArrayOutputStream();
                content.write(out);
                out.close();
            }
            /*byte[] helpBuffer;
             * ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
            int bytesRead;
            while ((bytesRead = in.read(dataBuffer)) >= 0) {
              if (bytesRead < dataBuffer.length) {
            helpBuffer = new byte[bytesRead]; // we need a buffer that only holds what to send for digesting
            System.arraycopy(dataBuffer, 0, helpBuffer, 0, bytesRead);
            pkcs11Session.digestUpdate(helpBuffer);
              } else pkcs11Session.digestUpdate(dataBuffer);
            }*/
            ByteArrayInputStream bais = new ByteArrayInputStream(out.toByteArray());
            MessageDigest softwareDigestEngine = MessageDigest.getInstance(PDF_SIGNATURE_DIGEST);
            int bytesRead;
            byte[] dataBuffer = new byte[BUFFER_SIZE];
            while ((bytesRead = bais.read(dataBuffer)) >= 0) {
                softwareDigestEngine.update(dataBuffer, 0, bytesRead);
            }
            byte[] hash = softwareDigestEngine.digest();

            digests.put(digestOID, hash.clone());

            AttributeTable signed;
            if (addDefaultAttributes) {
                Map parameters = getBaseParameters(contentType, digAlgId, hash);
                signed = (sAttr != null) ? sAttr.getAttributes(Collections.unmodifiableMap(parameters)) : null;
            } else
                signed = baseSignedTable;
            ASN1Set signedAttr = null;
            byte[] tmp;
            if (signed != null) {
                if (isCounterSignature) {
                    Hashtable tmpSigned = signed.toHashtable();
                    tmpSigned.remove(CMSAttributes.contentType);
                    signed = new AttributeTable(tmpSigned);
                }
                // TODO Validate proposed signed attributes
                signedAttr = getAttributeSet(signed);
                // sig must be composed from the DER encoding.
                tmp = signedAttr.getEncoded(ASN1Encodable.DER);
            } else {
                // TODO Use raw signature of the hash value instead
                ByteArrayOutputStream bOut = new ByteArrayOutputStream();
                if (content != null)
                    content.write(bOut);
                tmp = bOut.toByteArray();
            }
            // This signing operation is implemented in most of the drivers
            byte[] sigBytes = pkcs11Session.sign(tmp);
            ASN1Set unsignedAttr = null;
            SignerInfo signerInfo = new SignerInfo(signerIdentifier, digAlgId, signedAttr, encAlgId,
                    new DEROctetString(sigBytes), unsignedAttr);
            if (unsAttr != null) {
                Map parameters = getBaseParameters(contentType, digAlgId, hash);
                parameters.put(CMSAttributeTableGenerator.SIGNATURE, sigBytes.clone());
                AttributeTable unsigned = unsAttr.getAttributes(Collections.unmodifiableMap(parameters));
                // TODO Validate proposed unsigned attributes
                unsignedAttr = getAttributeSet(unsigned);
            }
            return new SignerInfo(signerIdentifier, digAlgId, signedAttr, encAlgId, new DEROctetString(sigBytes),
                    unsignedAttr);
        }
    }

    public DNIePDFContentSigner(Session pkcs11Session, Module pkcs11Module, Token token, RSAPrivateKey signatureKey,
            X509Certificate certUser, X509Certificate certIntermediate, X509Certificate certCA) throws Exception {
        this.pkcs11Session = pkcs11Session;
        this.pkcs11Module = pkcs11Module;
        this.token = token;
        this.signatureKey = signatureKey;
        this.certCA = certCA;
        this.certUser = certUser;
        this.certIntermediate = certIntermediate;
        CertStore certsAndCRLs = CertStore.getInstance(CERT_STORE_TYPE,
                new CollectionCertStoreParameters(Arrays.asList(getCertificateChain())), PROVIDER);
        ;
        addCertificatesAndCRLs(certsAndCRLs);
    }

    public Certificate[] getCertificateChain() {
        List<Certificate> certList = new ArrayList();
        //The order is important
        if (certUser != null)
            certList.add(certUser);
        if (certIntermediate != null)
            certList.add(certIntermediate);
        if (certCA != null)
            certList.add(certCA);
        return certList.toArray(new Certificate[certList.size()]);
    }

    public CMSSignedData getCMSSignedData(String eContentType, CMSProcessable content, boolean encapsulate,
            Provider sigProvider, boolean addDefaultAttributes, List signerInfs) throws Exception {
        // TODO if (signerInfs.isEmpty()){
        //            /* RFC 3852 5.2
        //             * "In the degenerate case where there are no signers, the
        //             * EncapsulatedContentInfo value being "signed" is irrelevant.  In this
        //             * case, the content type within the EncapsulatedContentInfo value being
        //             * "signed" MUST be id-data (as defined in section 4), and the content
        //             * field of the EncapsulatedContentInfo value MUST be omitted."
        //             */
        //            if (encapsulate) {
        //                throw new IllegalArgumentException("no signers, encapsulate must be false");
        //            } if (!DATA.equals(eContentType)) {
        //                throw new IllegalArgumentException("no signers, eContentType must be id-data");
        //            }
        //        }
        //        if (!DATA.equals(eContentType)) {
        //            /* RFC 3852 5.3
        //             * [The 'signedAttrs']...
        //             * field is optional, but it MUST be present if the content type of
        //             * the EncapsulatedContentInfo value being signed is not id-data.
        //             */
        //            // TODO signedAttrs must be present for all signers
        //        }
        ASN1EncodableVector digestAlgs = new ASN1EncodableVector();
        ASN1EncodableVector signerInfos = new ASN1EncodableVector();
        digests.clear(); // clear the current preserved digest state
        Iterator it = _signers.iterator();
        while (it.hasNext()) {
            SignerInformation signer = (SignerInformation) it.next();
            digestAlgs.add(CMSUtils.fixAlgID(signer.getDigestAlgorithmID()));
            signerInfos.add(signer.toSignerInfo());
        }
        boolean isCounterSignature = (eContentType == null);
        ASN1ObjectIdentifier contentTypeOID = isCounterSignature ? CMSObjectIdentifiers.data
                : new ASN1ObjectIdentifier(eContentType);
        it = signerInfs.iterator();
        while (it.hasNext()) {
            SignerInf signer = (SignerInf) it.next();
            log.info("signer.signerIdentifier: " + signer.signerIdentifier.toASN1Object().toString());
            digestAlgs.add(signer.getDigestAlgorithmID());
            signerInfos.add(signer.toSignerInfo(contentTypeOID, content, rand, null, addDefaultAttributes,
                    isCounterSignature));
        }
        ASN1Set certificates = null;
        if (!certs.isEmpty())
            certificates = CMSUtils.createBerSetFromList(certs);
        ASN1Set certrevlist = null;
        if (!crls.isEmpty())
            certrevlist = CMSUtils.createBerSetFromList(crls);
        ASN1OctetString octs = null;
        if (encapsulate && content != null) {
            ByteArrayOutputStream bOut = new ByteArrayOutputStream();
            content.write(bOut);
            octs = new BERConstructedOctetString(bOut.toByteArray());
        }
        ContentInfo encInfo = new ContentInfo(contentTypeOID, octs);
        SignedData sd = new SignedData(new DERSet(digestAlgs), encInfo, certificates, certrevlist,
                new DERSet(signerInfos));
        ContentInfo contentInfo = new ContentInfo(CMSObjectIdentifiers.signedData, sd);
        return new CMSSignedData(content, contentInfo);
    }

    public static DNIePDFContentSigner getInstance(char[] password, Mechanism signatureMechanism) throws Exception {
        log.info("getInstance");
        DNIePDFContentSigner instance = null;
        instanceSignatureMechanism = signatureMechanism;
        Session pkcs11Session;
        Module pkcs11Module;
        Token token;
        RSAPrivateKey signatureKey;
        X509Certificate certUser = null;
        X509Certificate certIntermediate = null;
        X509Certificate certCA = null;
        try {
            pkcs11Module = Module.getInstance(OSValidator.getPKCS11ModulePath());
            pkcs11Module.initialize(null);
            final SlotReader lectorSlots = new SlotReader(
                    pkcs11Module.getSlotList(Module.SlotRequirement.TOKEN_PRESENT));
            token = lectorSlots.getSelected().getToken();
            pkcs11Session = token.openSession(Token.SessionType.SERIAL_SESSION,
                    Token.SessionReadWriteBehavior.RO_SESSION, null, null);
            pkcs11Session.login(Session.UserType.USER, password);
            RSAPrivateKey templateSignatureKey = new RSAPrivateKey();
            templateSignatureKey.getSign().setBooleanValue(Boolean.TRUE);
            templateSignatureKey.getLabel().setCharArrayValue(DNIe_SIGN_PRIVATE_KEY_LABEL.toCharArray());
            pkcs11Session.findObjectsInit(templateSignatureKey);
            Object[] foundSignatureKeyObjects = pkcs11Session.findObjects(1); // find first
            if (foundSignatureKeyObjects.length > 0) {
                signatureKey = (RSAPrivateKey) foundSignatureKeyObjects[0];
                pkcs11Session.signInit(signatureMechanism, signatureKey);
            } else {
                throw new Exception(ContextVS.getInstance().getMessage("keyNotFoundMsg"));
            }
            pkcs11Session.findObjectsFinal();
            X509PublicKeyCertificate certificateTemplate = new X509PublicKeyCertificate();
            pkcs11Session.findObjectsInit(certificateTemplate);
            Object[] tokenCertificateObjects;
            certCA = CertUtils.loadCertificate(
                    FileUtils.getBytesFromFile(new File(ContextVS.APPDIR + ContextVS.CERT_RAIZ_PATH)));
            while ((tokenCertificateObjects = pkcs11Session.findObjects(1)).length > 0) {
                iaik.pkcs.pkcs11.objects.Object object = (Object) tokenCertificateObjects[0];
                Hashtable attributes = object.getAttributeTable();
                ByteArrayAttribute valueAttribute = (ByteArrayAttribute) attributes.get(Attribute.VALUE);
                byte[] value = valueAttribute.getByteArrayValue();
                X509PublicKeyCertificate cert = (X509PublicKeyCertificate) tokenCertificateObjects[0];
                if (CERT_SIGN.equals(cert.getLabel().toString())) {
                    certUser = (X509Certificate) CertUtils.loadCertificate(value);
                    ContextVS.getInstance().setSessionUser(UserVS.getUserVS(certUser));
                } else if (CERT_CA.equals(cert.getLabel().toString())) {
                    certIntermediate = (X509Certificate) CertUtils.loadCertificate(value);
                }
            }
            pkcs11Session.findObjectsFinal();
            //Collection<X500Principal> collection = new ArrayList<X500Principal>();
            //collection.add(certUser.getIssuerX500Principal());
            //X509CRLSelector selector = new X509CRLSelector();
            //selector.setIssuers(collection);
        } catch (Exception ex) {
            log.log(Level.SEVERE, ex.getMessage(), ex);
            if (ex instanceof ArrayIndexOutOfBoundsException) {
                throw new ExceptionVS(ContextVS.getInstance().getMessage("smartCardReaderErrorMsg"));
            }
            if ("CKR_PIN_INCORRECT".equals(ex.getMessage())) {
                throw new ExceptionVS(ContextVS.getInstance().getMessage("passwordErrorMsg"));
            }
            if ("CKR_HOST_MEMORY".equals(ex.getMessage())) {
                throw new ExceptionVS(ContextVS.getInstance().getMessage("smartCardReaderErrorMsg"));
            }
            throw ex;
        }
        instance = new DNIePDFContentSigner(pkcs11Session, pkcs11Module, token, signatureKey, certUser,
                certIntermediate, certCA);
        return instance;
    }

    public CMSSignedData genSignedData(byte[] contentBytes, CMSAttributeTableGenerator unsAttr) throws Exception {
        List signerInfs = new ArrayList();
        SignerInf signerInf = new SignerInf(CMSUtils.getSignerIdentifier(certUser), pkcs11Session, unsAttr);
        signerInfs.add(signerInf);
        CMSProcessable content = new CMSProcessableByteArray(contentBytes);
        CMSSignedData signedData = getCMSSignedData(CMSSignedGenerator.DATA, content, true,
                CMSUtils.getProvider("BC"), true, signerInfs);
        return signedData;
    }

    public void closeSession() {
        log.info("closeSession");
        try {
            if (token != null && token.getTokenInfo() != null && token.getTokenInfo().isTokenInitialized()) {
                token.closeAllSessions();
            }
            if (pkcs11Session != null) {
                pkcs11Session.closeSession();
                pkcs11Session = null;
            }
            if (pkcs11Module != null) {
                pkcs11Module.finalize(null);
                pkcs11Module = null;
            }
            System.gc();
        } catch (TokenException ex) {
            log.log(Level.SEVERE, ex.getMessage(), ex);
        }
    }

    private static class SlotReader {

        Slot[] slots;

        public SlotReader(Slot[] slots) {
            this.slots = slots;
        }

        public Slot getSelected() {
            return slots[0];
        }
    }

}