eu.europa.ec.markt.dss.validation.cades.CAdESSignature.java Source code

Java tutorial

Introduction

Here is the source code for eu.europa.ec.markt.dss.validation.cades.CAdESSignature.java

Source

/*
 * DSS - Digital Signature Services
 *
 * Copyright (C) 2011 European Commission, Directorate-General Internal Market and Services (DG MARKT), B-1049 Bruxelles/Brussel
 *
 * Developed by: 2011 ARHS Developments S.A. (rue Nicolas Bov 2B, L-1253 Luxembourg) http://www.arhs-developments.com
 *
 * This file is part of the "DSS - Digital Signature Services" project.
 *
 * "DSS - Digital Signature Services" is free software: you can redistribute it and/or modify it under the terms of
 * the GNU Lesser General Public License as published by the Free Software Foundation, either version 2.1 of the
 * License, or (at your option) any later version.
 *
 * DSS is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along with
 * "DSS - Digital Signature Services".  If not, see <http://www.gnu.org/licenses/>.
 */

package eu.europa.ec.markt.dss.validation.cades;

import eu.europa.ec.markt.dss.signature.Document;
import eu.europa.ec.markt.dss.validation.AdvancedSignature;
import eu.europa.ec.markt.dss.validation.CRLRef;
import eu.europa.ec.markt.dss.validation.CertificateRef;
import eu.europa.ec.markt.dss.validation.OCSPRef;
import eu.europa.ec.markt.dss.validation.PolicyValue;
import eu.europa.ec.markt.dss.validation.SignatureFormat;
import eu.europa.ec.markt.dss.validation.certificate.CertificateSource;
import eu.europa.ec.markt.dss.validation.x509.TimestampToken;

import java.io.IOException;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.io.output.ByteArrayOutputStream;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Set;
import org.bouncycastle.asn1.ASN1UTCTime;
import org.bouncycastle.asn1.BERConstructedOctetString;
import org.bouncycastle.asn1.DEREncodable;
import org.bouncycastle.asn1.DERNull;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DEROutputStream;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.cms.Attribute;
import org.bouncycastle.asn1.cms.AttributeTable;
import org.bouncycastle.asn1.cms.ContentInfo;
import org.bouncycastle.asn1.cms.SignedData;
import org.bouncycastle.asn1.esf.CrlOcspRef;
import org.bouncycastle.asn1.esf.CrlValidatedID;
import org.bouncycastle.asn1.esf.OcspResponsesID;
import org.bouncycastle.asn1.esf.SignaturePolicyId;
import org.bouncycastle.asn1.esf.SignerAttribute;
import org.bouncycastle.asn1.ess.OtherCertID;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x509.Time;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.CMSSignedDataParser;
import org.bouncycastle.cms.CMSTypedStream;
import org.bouncycastle.cms.SignerId;
import org.bouncycastle.cms.SignerInformation;
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
import org.bouncycastle.ocsp.BasicOCSPResp;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.tsp.TimeStampToken;

/**
 * 
 * CAdES Signature class helper
 * 
 *
 * @version $Revision: 1867 $ - $Date: 2013-04-08 13:44:56 +0200 (Mon, 08 Apr 2013) $
 */

public class CAdESSignature implements AdvancedSignature {

    public static final ASN1ObjectIdentifier id_aa_ets_archiveTimestampV2 = PKCSObjectIdentifiers.id_aa
            .branch("48");

    private static Logger LOG = Logger.getLogger(CAdESSignature.class.getName());

    private CMSSignedData cmsSignedData;
    private SignerInformation signerInformation;

    /**
     * 
     * The default constructor for CAdESSignature.
     * 
     * @param data
     * @throws CMSException
     */
    public CAdESSignature(byte[] data) throws CMSException {
        this(new CMSSignedData(data));
    }

    /**
     * 
     * The default constructor for CAdESSignature.
     * 
     * @param data
     * @throws CMSException
     */
    public CAdESSignature(CMSSignedData cms) {
        this(cms, (SignerInformation) cms.getSignerInfos().getSigners().iterator().next());
    }

    /**
     * 
     * The default constructor for CAdESSignature.
     * 
     * @param data
     * @throws CMSException
     */
    public CAdESSignature(CMSSignedData cms, SignerInformation signerInformation) {
        this.cmsSignedData = cms;
        this.signerInformation = signerInformation;
    }

    /**
     * 
     * The default constructor for CAdESSignature.
     * 
     * @param data
     * @throws CMSException
     */
    public CAdESSignature(CMSSignedData cms, SignerId id) {
        this(cms, cms.getSignerInfos().get(id));
    }

    @Override
    public SignatureFormat getSignatureFormat() {
        return SignatureFormat.CAdES;
    }

    @Override
    public CAdESCertificateSource getCertificateSource() {
        return new CAdESCertificateSource(cmsSignedData, signerInformation.getSID(), false);
    }

    @Override
    public CertificateSource getExtendedCertificateSource() {
        return new CAdESCertificateSource(cmsSignedData, signerInformation.getSID(), true);
    }

    @Override
    public CAdESCRLSource getCRLSource() {
        return new CAdESCRLSource(cmsSignedData, signerInformation.getSID());
    }

    @Override
    public CAdESOCSPSource getOCSPSource() {
        return new CAdESOCSPSource(cmsSignedData, signerInformation.getSID());
    }

    @Override
    public X509Certificate getSigningCertificate() {

        LOG.info("SignerInformation " + signerInformation.getSID());
        Collection<X509Certificate> certs = getCertificates();
        for (X509Certificate cert : certs) {
            LOG.info("Test match for certificate " + cert.getSubjectDN().getName());
            if (signerInformation.getSID().match(cert)) {
                return cert;
            }
        }

        return null;
    }

    @Override
    public List<X509Certificate> getCertificates() {
        return getCertificateSource().getCertificates();
    }

    @Override
    public PolicyValue getPolicyId() {
        if (signerInformation.getSignedAttributes() == null) {
            return null;
        }
        Attribute sigPolicytAttr = signerInformation.getSignedAttributes()
                .get(PKCSObjectIdentifiers.id_aa_ets_sigPolicyId);
        if (sigPolicytAttr == null) {
            return null;
        }

        if (sigPolicytAttr.getAttrValues().getObjectAt(0) instanceof DERNull) {
            return new PolicyValue();
        }

        SignaturePolicyId sigPolicy = null;
        sigPolicy = SignaturePolicyId.getInstance(sigPolicytAttr.getAttrValues().getObjectAt(0));

        if (sigPolicy == null) {
            return new PolicyValue();
        }

        return new PolicyValue(sigPolicy.getSigPolicyId().getId());
    }

    @Override
    public Date getSigningTime() {
        if (signerInformation.getSignedAttributes() != null && signerInformation.getSignedAttributes()
                .get(PKCSObjectIdentifiers.pkcs_9_at_signingTime) != null) {
            ASN1Set set = signerInformation.getSignedAttributes().get(PKCSObjectIdentifiers.pkcs_9_at_signingTime)
                    .getAttrValues();
            try {
                Object o = set.getObjectAt(0);
                if (o instanceof ASN1UTCTime) {
                    return ((ASN1UTCTime) o).getDate();
                }
                if (o instanceof Time) {
                    return ((Time) o).getDate();
                }
                LOG.log(Level.SEVERE, "Error when reading signing time. Unrecognized " + o.getClass());
            } catch (Exception ex) {
                LOG.log(Level.SEVERE, "Error when reading signing time ", ex);
                return null;
            }
        }
        return null;
    }

    /**
     * @return the cmsSignedData
     */
    public CMSSignedData getCmsSignedData() {
        return cmsSignedData;
    }

    @Override
    public String getLocation() {
        return null;
    }

    @Override
    public String[] getClaimedSignerRoles() {

        if (signerInformation.getSignedAttributes() == null) {
            return null;
        }

        Attribute signerAttrAttr = signerInformation.getSignedAttributes()
                .get(PKCSObjectIdentifiers.id_aa_ets_signerAttr);
        if (signerAttrAttr == null) {
            return null;
        }

        SignerAttribute signerAttr = null;
        signerAttr = SignerAttribute.getInstance(signerAttrAttr.getAttrValues().getObjectAt(0));

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

        String[] ret = new String[signerAttr.getClaimedAttributes().size()];
        for (int i = 0; i < signerAttr.getClaimedAttributes().size(); i++) {
            if (signerAttr.getClaimedAttributes().getObjectAt(i) instanceof DEROctetString) {
                ret[i] = new String(
                        ((DEROctetString) signerAttr.getClaimedAttributes().getObjectAt(i)).getOctets());

            } else {
                ret[i] = signerAttr.getClaimedAttributes().getObjectAt(i).toString();
            }
        }

        return ret;
    }

    private List<TimestampToken> getTimestampList(ASN1ObjectIdentifier attrType,
            TimestampToken.TimestampType timestampType) {
        if (signerInformation.getUnsignedAttributes() != null) {
            Attribute timeStampAttr = signerInformation.getUnsignedAttributes().get(attrType);
            if (timeStampAttr == null) {
                return null;
            }

            List<TimestampToken> tstokens = new ArrayList<TimestampToken>();
            for (ASN1Encodable value : timeStampAttr.getAttrValues().toArray()) {
                try {
                    TimeStampToken token = new TimeStampToken(new CMSSignedData(value.getDEREncoded()));
                    tstokens.add(new TimestampToken(token, timestampType));
                } catch (Exception e) {
                    throw new RuntimeException("Parsing error", e);
                }
            }

            return tstokens;
        } else {
            return null;
        }
    }

    protected List<TimestampToken> getContentTimestamps() {
        return getTimestampList(PKCSObjectIdentifiers.id_aa_ets_contentTimestamp,
                TimestampToken.TimestampType.CONTENT_TIMESTAMP);
    }

    @Override
    public List<TimestampToken> getSignatureTimestamps() throws RuntimeException {
        return getTimestampList(PKCSObjectIdentifiers.id_aa_signatureTimeStampToken,
                TimestampToken.TimestampType.SIGNATURE_TIMESTAMP);
    }

    @Override
    public List<TimestampToken> getTimestampsX1() {
        return getTimestampList(PKCSObjectIdentifiers.id_aa_ets_escTimeStamp,
                TimestampToken.TimestampType.VALIDATION_DATA_TIMESTAMP);
    }

    @Override
    public List<TimestampToken> getTimestampsX2() {
        return getTimestampList(PKCSObjectIdentifiers.id_aa_ets_certCRLTimestamp,
                TimestampToken.TimestampType.VALIDATION_DATA_REFSONLY_TIMESTAMP);
    }

    @Override
    public List<TimestampToken> getArchiveTimestamps() {
        return getTimestampList(id_aa_ets_archiveTimestampV2, TimestampToken.TimestampType.ARCHIVE_TIMESTAMP);
    }

    @Override
    public String getSignatureAlgorithm() {
        return signerInformation.getEncryptionAlgOID();
    }

    @Override
    public boolean checkIntegrity(Document detachedDocument) {
        JcaSimpleSignerInfoVerifierBuilder verifier = new JcaSimpleSignerInfoVerifierBuilder();
        try {
            boolean ret = false;

            SignerInformation si = null;
            if (detachedDocument != null) {
                // Recreate a SignerInformation with the content using a CMSSignedDataParser
                CMSSignedDataParser sp = new CMSSignedDataParser(new CMSTypedStream(detachedDocument.openStream()),
                        cmsSignedData.getEncoded());
                sp.getSignedContent().drain();
                si = sp.getSignerInfos().get(signerInformation.getSID());
            } else {
                si = this.signerInformation;
            }

            ret = si.verify(verifier.build(getSigningCertificate()));

            return ret;

        } catch (OperatorCreationException e) {
            return false;
        } catch (CMSException e) {
            return false;
        } catch (IOException e) {
            return false;
        }
    }

    @Override
    public String getContentType() {
        return signerInformation.getContentType().toString();
    }

    /**
     * @return the signerInformation
     */
    public SignerInformation getSignerInformation() {
        return signerInformation;
    }

    @Override
    public List<AdvancedSignature> getCounterSignatures() {

        List<AdvancedSignature> counterSigs = new ArrayList<AdvancedSignature>();
        for (Object o : this.signerInformation.getCounterSignatures().getSigners()) {
            SignerInformation i = (SignerInformation) o;

            CAdESSignature info = new CAdESSignature(this.cmsSignedData, i.getSID());
            counterSigs.add(info);
        }

        return counterSigs;
    }

    @Override
    public List<CertificateRef> getCertificateRefs() {
        List<CertificateRef> list = new ArrayList<CertificateRef>();

        if (signerInformation.getUnsignedAttributes() != null) {
            Attribute completeCertRefsAttr = signerInformation.getUnsignedAttributes()
                    .get(PKCSObjectIdentifiers.id_aa_ets_certificateRefs);
            if (completeCertRefsAttr != null && completeCertRefsAttr.getAttrValues().size() > 0) {
                DERSequence completeCertificateRefs = (DERSequence) completeCertRefsAttr.getAttrValues()
                        .getObjectAt(0);
                for (int i1 = 0; i1 < completeCertificateRefs.size(); i1++) {
                    OtherCertID otherCertId = OtherCertID.getInstance(completeCertificateRefs.getObjectAt(i1));
                    CertificateRef certId = new CertificateRef();
                    certId.setDigestAlgorithm(otherCertId.getAlgorithmHash().getAlgorithm().getId());
                    certId.setDigestValue(otherCertId.getCertHash());
                    if (otherCertId.getIssuerSerial() != null) {
                        if (otherCertId.getIssuerSerial().getIssuer() != null) {
                            certId.setIssuerName(otherCertId.getIssuerSerial().getIssuer().toString());
                        }
                        if (otherCertId.getIssuerSerial().getSerial() != null) {
                            certId.setIssuerSerial(otherCertId.getIssuerSerial().getSerial().toString());
                        }
                    }
                    list.add(certId);
                }
            }
        }

        return list;
    }

    @Override
    public List<CRLRef> getCRLRefs() {
        List<CRLRef> list = new ArrayList<CRLRef>();

        if (signerInformation.getUnsignedAttributes() != null) {
            Attribute completeRevocationRefsAttr = signerInformation.getUnsignedAttributes()
                    .get(PKCSObjectIdentifiers.id_aa_ets_revocationRefs);
            if (completeRevocationRefsAttr != null && completeRevocationRefsAttr.getAttrValues().size() > 0) {
                DERSequence completeCertificateRefs = (DERSequence) completeRevocationRefsAttr.getAttrValues()
                        .getObjectAt(0);
                for (int i1 = 0; i1 < completeCertificateRefs.size(); i1++) {
                    CrlOcspRef otherCertId = CrlOcspRef.getInstance(completeCertificateRefs.getObjectAt(i1));
                    for (CrlValidatedID id : otherCertId.getCrlids().getCrls()) {
                        list.add(new CRLRef(id));
                    }
                }
            }
        }

        return list;
    }

    @Override
    public List<OCSPRef> getOCSPRefs() {
        List<OCSPRef> list = new ArrayList<OCSPRef>();

        if (signerInformation.getUnsignedAttributes() != null) {
            Attribute completeRevocationRefsAttr = signerInformation.getUnsignedAttributes()
                    .get(PKCSObjectIdentifiers.id_aa_ets_revocationRefs);
            if (completeRevocationRefsAttr != null && completeRevocationRefsAttr.getAttrValues().size() > 0) {
                DERSequence completeRevocationRefs = (DERSequence) completeRevocationRefsAttr.getAttrValues()
                        .getObjectAt(0);
                for (int i1 = 0; i1 < completeRevocationRefs.size(); i1++) {
                    CrlOcspRef otherCertId = CrlOcspRef.getInstance(completeRevocationRefs.getObjectAt(i1));

                    for (OcspResponsesID id : otherCertId.getOcspids().getOcspResponses()) {
                        list.add(new OCSPRef(id, true));
                    }
                }
            }
        }

        return list;
    }

    @Override
    public List<X509CRL> getCRLs() {
        return getCRLSource().getCRLsFromSignature();
    }

    @Override
    public List<BasicOCSPResp> getOCSPs() {
        return getOCSPSource().getOCSPResponsesFromSignature();
    }

    @Override
    public byte[] getSignatureTimestampData() {
        return signerInformation.getSignature();
    }

    @Override
    public byte[] getTimestampX1Data() {
        try {
            ByteArrayOutputStream toTimestamp = new ByteArrayOutputStream();

            toTimestamp.write(signerInformation.getSignature());

            /*
             * We don't include the outer SEQUENCE, only the attrType and attrValues as stated by the TS 6.3.5, NOTE 2
             */
            if (signerInformation.getUnsignedAttributes() != null) {
                toTimestamp.write(signerInformation.getUnsignedAttributes()
                        .get(PKCSObjectIdentifiers.id_aa_signatureTimeStampToken).getAttrType().getDEREncoded());
                toTimestamp.write(signerInformation.getUnsignedAttributes()
                        .get(PKCSObjectIdentifiers.id_aa_signatureTimeStampToken).getAttrValues().getDEREncoded());
            }

            /* Those are common to Type 1 and Type 2 */
            toTimestamp.write(getTimestampX2Data());

            return toTimestamp.toByteArray();
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }

    @Override
    public byte[] getTimestampX2Data() {
        try {
            ByteArrayOutputStream toTimestamp = new ByteArrayOutputStream();

            /* Those are common to Type 1 and Type 2 */
            if (signerInformation.getUnsignedAttributes() != null) {
                toTimestamp.write(signerInformation.getUnsignedAttributes()
                        .get(PKCSObjectIdentifiers.id_aa_ets_certificateRefs).getAttrType().getDEREncoded());
                toTimestamp.write(signerInformation.getUnsignedAttributes()
                        .get(PKCSObjectIdentifiers.id_aa_ets_certificateRefs).getAttrValues().getDEREncoded());
                toTimestamp.write(signerInformation.getUnsignedAttributes()
                        .get(PKCSObjectIdentifiers.id_aa_ets_revocationRefs).getAttrType().getDEREncoded());
                toTimestamp.write(signerInformation.getUnsignedAttributes()
                        .get(PKCSObjectIdentifiers.id_aa_ets_revocationRefs).getAttrValues().getDEREncoded());
            }

            return toTimestamp.toByteArray();
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }

    }

    @Override
    public byte[] getArchiveTimestampData(int index, Document originalDocument) throws IOException {

        ByteArrayOutputStream toTimestamp = new ByteArrayOutputStream();

        ContentInfo contentInfo = cmsSignedData.getContentInfo();
        SignedData signedData = SignedData.getInstance(contentInfo.getContent());

        /* The encapContentInfo should always be present according to the standard, but sometimes it's omitted */
        // 5.4.1
        if (signedData.getEncapContentInfo() == null || signedData.getEncapContentInfo().getContent() == null) {
            /* Detached signatures have either no encapContentInfo in signedData, or it exists but has no eContent */
            if (originalDocument != null) {
                toTimestamp.write(originalDocument.openStream());
            } else {
                throw new RuntimeException("Signature is detached and no original data provided.");
            }
        } else {

            ContentInfo content = signedData.getEncapContentInfo();
            DEROctetString octet = (DEROctetString) content.getContent();

            ContentInfo info2 = new ContentInfo(new ASN1ObjectIdentifier("1.2.840.113549.1.7.1"),
                    new BERConstructedOctetString(octet.getOctets()));
            toTimestamp.write(info2.getEncoded());
        }

        if (signedData.getCertificates() != null) {
            DEROutputStream output = new DEROutputStream(toTimestamp);
            output.writeObject(signedData.getCertificates());
            output.close();
        }

        if (signedData.getCRLs() != null) {
            toTimestamp.write(signedData.getCRLs().getEncoded());
        }

        if (signerInformation.getUnsignedAttributes() != null) {
            ASN1EncodableVector original = signerInformation.getUnsignedAttributes().toASN1EncodableVector();
            List<Attribute> timeStampToRemove = getTimeStampToRemove(index);
            ASN1EncodableVector filtered = new ASN1EncodableVector();
            for (int i = 0; i < original.size(); i++) {
                DEREncodable enc = original.get(i);
                if (!timeStampToRemove.contains(enc)) {
                    filtered.add(original.get(i));
                }
            }
            SignerInformation filteredInfo = SignerInformation.replaceUnsignedAttributes(signerInformation,
                    new AttributeTable(filtered));

            toTimestamp.write(filteredInfo.toASN1Structure().getEncoded());
        }

        return toTimestamp.toByteArray();
    }

    private class AttributeTimeStampComparator implements Comparator<Attribute> {
        @Override
        public int compare(Attribute o1, Attribute o2) {
            try {
                TimeStampToken t1 = new TimeStampToken(
                        new CMSSignedData(o1.getAttrValues().getObjectAt(0).getDERObject().getDEREncoded()));
                TimeStampToken t2 = new TimeStampToken(
                        new CMSSignedData(o2.getAttrValues().getObjectAt(0).getDERObject().getDEREncoded()));
                return -t1.getTimeStampInfo().getGenTime().compareTo(t2.getTimeStampInfo().getGenTime());
            } catch (Exception e) {
                throw new RuntimeException("Cannot read original ArchiveTimeStamp", e);
            }
        }
    }

    private List<Attribute> getTimeStampToRemove(int archiveTimeStampToKeep) {
        List<Attribute> ts = new ArrayList<Attribute>();
        /*
         * We need to remove every ArchiveTimeStamp with index < index. Every timestamp is retrieved, then the list is
         * sorted
         */
        if (signerInformation.getUnsignedAttributes() != null) {
            ASN1EncodableVector v = signerInformation.getUnsignedAttributes().getAll(id_aa_ets_archiveTimestampV2);

            for (int i = 0; i < v.size(); i++) {
                DEREncodable enc = v.get(i);
                ts.add((Attribute) enc);
            }

            Collections.sort(ts, new AttributeTimeStampComparator());

            /*
             * TS will contain the list of TimeStamps we must remove the (index) first timestamp. The list is sorted
             * with timestaps descending.
             */
            for (int i = 0; i < archiveTimeStampToKeep; i++) {
                ts.remove(0);
            }

        }
        return ts;
    }
}