eu.europa.ec.markt.dss.signature.cades.CAdESLevelBaselineB.java Source code

Java tutorial

Introduction

Here is the source code for eu.europa.ec.markt.dss.signature.cades.CAdESLevelBaselineB.java

Source

/*
 * DSS - Digital Signature Services
 *
 * Copyright (C) 2013 European Commission, Directorate-General Internal Market and Services (DG MARKT), B-1049 Bruxelles/Brussel
 *
 * Developed by: 2013 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.signature.cades;

import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Date;
import java.util.Hashtable;
import java.util.List;
import java.util.Random;

import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1GeneralizedTime;
import org.bouncycastle.asn1.ASN1Object;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DERSet;
import org.bouncycastle.asn1.DERUTF8String;
import org.bouncycastle.asn1.cms.Attribute;
import org.bouncycastle.asn1.cms.AttributeTable;
import org.bouncycastle.asn1.esf.OtherHashAlgAndValue;
import org.bouncycastle.asn1.esf.SignaturePolicyId;
import org.bouncycastle.asn1.esf.SignaturePolicyIdentifier;
import org.bouncycastle.asn1.esf.SignerAttribute;
import org.bouncycastle.asn1.esf.SignerLocation;
import org.bouncycastle.asn1.ess.ContentHints;
import org.bouncycastle.asn1.ess.ContentIdentifier;
import org.bouncycastle.asn1.ess.ESSCertID;
import org.bouncycastle.asn1.ess.ESSCertIDv2;
import org.bouncycastle.asn1.ess.SigningCertificate;
import org.bouncycastle.asn1.ess.SigningCertificateV2;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.IssuerSerial;
import org.bouncycastle.asn1.x509.Time;
import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import eu.europa.ec.markt.dss.DSSASN1Utils;
import eu.europa.ec.markt.dss.DSSUtils;
import eu.europa.ec.markt.dss.DigestAlgorithm;
import eu.europa.ec.markt.dss.OID;
import eu.europa.ec.markt.dss.exception.DSSException;
import eu.europa.ec.markt.dss.parameter.BLevelParameters;
import eu.europa.ec.markt.dss.parameter.BLevelParameters.Policy;
import eu.europa.ec.markt.dss.parameter.SignatureParameters;
import eu.europa.ec.markt.dss.signature.DSSDocument;
import eu.europa.ec.markt.dss.signature.MimeType;
import eu.europa.ec.markt.dss.validation102853.TimestampToken;

/**
 * This class holds the CAdES-B signature profile; it supports the inclusion of the mandatory signed
 * id_aa_ets_sigPolicyId attribute as specified in ETSI TS 101 733 V1.8.1, clause 5.8.1.
 * <p/>
 *
 * @version $Revision$ - $Date$
 */
public class CAdESLevelBaselineB {

    private static final Logger LOG = LoggerFactory.getLogger(CAdESLevelBaselineB.class);

    private final boolean padesUsage;

    /**
     * The default constructor for CAdESLevelBaselineB.
     */
    public CAdESLevelBaselineB() {
        this(false);
    }

    /**
     * The default constructor for CAdESLevelBaselineB.
     */
    public CAdESLevelBaselineB(boolean padesUsage) {

        this.padesUsage = padesUsage;
    }

    /**
     * Return the table of unsigned properties.
     *
     * @return
     */
    public AttributeTable getUnsignedAttributes() {

        return new AttributeTable(new Hashtable<ASN1ObjectIdentifier, ASN1Encodable>());
    }

    public AttributeTable getSignedAttributes(final SignatureParameters parameters) {

        ASN1EncodableVector signedAttributes = new ASN1EncodableVector();

        addSigningCertificateAttribute(parameters, signedAttributes);
        addSigningTimeAttribute(parameters, signedAttributes);
        addSignerAttribute(parameters, signedAttributes);
        addSignaturePolicyId(parameters, signedAttributes);
        addContentHints(parameters, signedAttributes);
        addContentIdentifier(parameters, signedAttributes);
        addCommitmentType(parameters, signedAttributes);
        addSignerLocation(parameters, signedAttributes);
        addContentTimestamps(parameters, signedAttributes);

        // mime-type attribute breaks parallel signatures by adding PKCS7 as a mime-type for subsequent signers.
        // This attribute is not mandatory, so it has been disabled.
        // signedAttributes = addMimeType(document, signedAttributes);

        final AttributeTable signedAttributesTable = new AttributeTable(signedAttributes);
        return signedAttributesTable;
    }

    /**
     * 5.11.5 mime-type Attribute
     * <p/>
     * The mime-type attribute is an attribute that lets the signature generator indicate the mime-type of the signed data. It
     * is similar in spirit to the contentDescription field of the content-hints attribute, but can be used without a multilayered
     * document.
     * <p/>
     * The mime-type attribute shall be a signed attribute.
     * <p/>
     * The following object identifier identifies the mime-type attribute:
     * id-aa-ets-mimeType OBJECT IDENTIFIER ::= { itu-t(0) identified-organization(4) etsi(0) electronicsignature-
     * standard (1733) attributes(2) 1 }
     * <p/>
     * mime-type attribute values have ASN.1 type UTF8String:
     * <p/>
     * mimeType::= UTF8String
     * <p/>
     * The mimeType is used to indicate the encoding of the signed data, in accordance with the rules defined in
     * RFC 2045 [6]; see annex F for an example of structured contents and MIME.
     * Only a single mime-type attribute shall be present.
     * <p/>
     * The mime-type attribute shall not be used within a countersignature.
     *
     * @param document
     * @param signedAttributes
     */
    private void addMimeType(final DSSDocument document, final ASN1EncodableVector signedAttributes) {

        if (!padesUsage) {
            final MimeType mimeType = document.getMimeType();
            if (mimeType != null && DSSUtils.isNotBlank(mimeType.getMimeTypeString())) {

                final org.bouncycastle.asn1.cms.Attribute attribute = new org.bouncycastle.asn1.cms.Attribute(
                        OID.id_aa_ets_mimeType, new DERSet(new DERUTF8String(mimeType.getMimeTypeString())));
                signedAttributes.add(attribute);
            }
        }
    }

    /**
     * ETSI TS 101 733 V2.2.1 (2013-04)
     * 5.11.3 signer-attributes Attribute
     * NOTE 1: Only a single signer-attributes can be used.
     * <p/>
     * The signer-attributes attribute specifies additional attributes of the signer (e.g. role).
     * It may be either:
     *  claimed attributes of the signer; or
     *  certified attributes of the signer.
     * The signer-attributes attribute shall be a signed attribute.
     *
     * @param parameters
     * @param signedAttributes
     * @return
     */
    private void addSignerAttribute(final SignatureParameters parameters,
            final ASN1EncodableVector signedAttributes) {

        // In PAdES, the role is in the signature dictionary
        if (!padesUsage) {

            final List<String> claimedSignerRoles = parameters.bLevel().getClaimedSignerRoles();
            if (claimedSignerRoles != null) {

                List<org.bouncycastle.asn1.x509.Attribute> claimedAttributes = new ArrayList<org.bouncycastle.asn1.x509.Attribute>(
                        claimedSignerRoles.size());
                for (final String claimedSignerRole : claimedSignerRoles) {

                    final DERUTF8String roles = new DERUTF8String(claimedSignerRole);

                    //TODO: role attribute key (id_at_name) should be customizable
                    final org.bouncycastle.asn1.x509.Attribute id_aa_ets_signerAttr = new org.bouncycastle.asn1.x509.Attribute(
                            X509ObjectIdentifiers.id_at_name, new DERSet(roles));
                    claimedAttributes.add(id_aa_ets_signerAttr);
                }
                final org.bouncycastle.asn1.cms.Attribute attribute = new org.bouncycastle.asn1.cms.Attribute(
                        PKCSObjectIdentifiers.id_aa_ets_signerAttr, new DERSet(new SignerAttribute(claimedAttributes
                                .toArray(new org.bouncycastle.asn1.x509.Attribute[claimedAttributes.size()]))));
                signedAttributes.add(attribute);
            }
            //TODO: handle CertifiedAttributes ::= AttributeCertificate -- as defined in RFC 3281: see clause 4.1.
            // final List<String> certifiedSignerRoles = parameters.bLevel().getCertifiedSignerRoles();
        }
    }

    private void addSigningTimeAttribute(final SignatureParameters parameters,
            final ASN1EncodableVector signedAttributes) {

        if (!padesUsage) {
            /*
             * In PAdES, we don't include the signing time : ETSI TS 102 778-3 V1.2.1 (2010-07): 4.5.3 signing-time
               * Attribute
               */
            final Date signingDate = parameters.bLevel().getSigningDate();
            if (signingDate != null) {

                final DERSet attrValues = new DERSet(new Time(signingDate));
                final Attribute attribute = new Attribute(PKCSObjectIdentifiers.pkcs_9_at_signingTime, attrValues);
                signedAttributes.add(attribute);
            }
        }
    }

    /**
     * ETSI TS 101 733 V2.2.1 (2013-04)
     * 5.11.2 signer-location Attribute
     * The signer-location attribute specifies a mnemonic for an address associated with the signer at a particular
     * geographical (e.g. city) location. The mnemonic is registered in the country in which the signer is located and is used in
     * the provision of the Public Telegram Service (according to Recommendation ITU-T F.1 [11]).
     * The signer-location attribute shall be a signed attribute.
     *
     * @param parameters
     * @param signedAttributes
     * @return
     */
    private void addSignerLocation(final SignatureParameters parameters,
            final ASN1EncodableVector signedAttributes) {

        if (!padesUsage) {
            /*
             * In PAdES, the role is in the signature dictionary
               */
            final BLevelParameters.SignerLocation signerLocationParameter = parameters.bLevel().getSignerLocation();
            if (signerLocationParameter != null) {

                final DERUTF8String country = signerLocationParameter.getCountry() == null ? null
                        : new DERUTF8String(signerLocationParameter.getCountry());
                final DERUTF8String locality = signerLocationParameter.getLocality() == null ? null
                        : new DERUTF8String(signerLocationParameter.getLocality());
                final ASN1EncodableVector postalAddress = new ASN1EncodableVector();
                final List<String> postalAddressParameter = signerLocationParameter.getPostalAddress();
                if (postalAddressParameter != null) {

                    for (final String addressLine : postalAddressParameter) {

                        postalAddress.add(new DERUTF8String(addressLine));
                    }
                }
                final DERSequence derSequencePostalAddress = new DERSequence(postalAddress);
                final SignerLocation signerLocation = new SignerLocation(country, locality,
                        derSequencePostalAddress);
                final DERSet attrValues = new DERSet(signerLocation);
                final Attribute attribute = new Attribute(PKCSObjectIdentifiers.id_aa_ets_signerLocation,
                        attrValues);
                signedAttributes.add(attribute);
            }
        }
    }

    /**
     * ETSI TS 101 733 V2.2.1 (2013-04)
     * <p/>
     * 5.11.1 commitment-type-indication Attribute
     * There may be situations where a signer wants to explicitly indicate to a verifier that by signing the data, it illustrates a
     * type of commitment on behalf of the signer. The commitment-type-indication attribute conveys such
     * information.
     *
     * @param parameters
     * @param signedAttributes
     */
    private void addCommitmentType(final SignatureParameters parameters,
            final ASN1EncodableVector signedAttributes) {

        // TODO (19/08/2014): commitmentTypeQualifier is not implemented
        final BLevelParameters bLevelParameters = parameters.bLevel();

        final List<String> commitmentTypeIndications = bLevelParameters.getCommitmentTypeIndications();
        if (commitmentTypeIndications != null && !commitmentTypeIndications.isEmpty()) {

            final int size = commitmentTypeIndications.size();
            ASN1Encodable[] asn1Encodables = new ASN1Encodable[size];
            for (int ii = 0; ii < size; ii++) {

                final String commitmentTypeId = commitmentTypeIndications.get(ii);
                final ASN1ObjectIdentifier objectIdentifier = new ASN1ObjectIdentifier(commitmentTypeId);
                // final CommitmentTypeIndication commitmentTypeIndication = new CommitmentTypeIndication(objectIdentifier);
                //            final ASN1Primitive asn1Primitive = commitmentTypeIndication.toASN1Primitive();
                asn1Encodables[ii] = new DERSequence(objectIdentifier);
            }
            final DERSet attrValues = new DERSet(asn1Encodables);
            final Attribute attribute = new Attribute(PKCSObjectIdentifiers.id_aa_ets_commitmentType, attrValues);
            signedAttributes.add(attribute);
        }
    }

    /**
     * A content time-stamp allows a time-stamp token of the data to be signed to be incorporated into the signed information.
     * It provides proof of the existence of the data before the signature was created.
     * <p/>
     * A content time-stamp attribute is the time-stamp token of the signed data content before it is signed.
     * This attribute is a signed attribute.
     * Its object identifier is :
     * id-aa-ets-contentTimestamp OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-9(9) smime(16) id-aa(2) 20}
     * <p/>
     * Content time-stamp attribute values have ASN.1 type ContentTimestamp:
     * ContentTimestamp ::= TimeStampToken
     * <p/>
     * The value of messageImprint of TimeStampToken (as described in RFC 3161) is the hash of the message digest as defined in
     * ETSI standard 101733 v.2.2.1, clause 5.6.1.
     * <p/>
     * NOTE: content-time-stamp indicates that the signed information was formed before the date included in the content-time-stamp.
     * NOTE (bis): There is a small difference in treatment between the content-time-stamp and the archive-timestamp (ATSv2) when the signature
     * is attached. In that case, the content-time-stamp is computed on the raw data (without ASN.1 tag and length) whereas the archive-timestamp
     * is computed on data as read.
     *
     * @param parameters
     * @param signedAttributes
     * @return
     */
    private void addContentTimestamps(final SignatureParameters parameters,
            final ASN1EncodableVector signedAttributes) {

        if (parameters.getContentTimestamps() != null && !parameters.getContentTimestamps().isEmpty()) {

            final List<TimestampToken> contentTimestamps = parameters.getContentTimestamps();
            for (final TimestampToken contentTimestamp : contentTimestamps) {

                final ASN1Object asn1Object = DSSASN1Utils.toASN1Primitive(contentTimestamp.getEncoded());
                final DERSet attrValues = new DERSet(asn1Object);
                final Attribute attribute = new Attribute(PKCSObjectIdentifiers.id_aa_ets_contentTimestamp,
                        attrValues);
                signedAttributes.add(attribute);
            }
        }
    }

    /**
     * ETSI TS 101 733 V2.2.1 (2013-04)
     * <p/>
     * 5.10.3 content-hints Attribute
     * The content-hints attribute provides information on the innermost signed content of a multi-layer message where
     * one content is encapsulated in another.
     * The syntax of the content-hints attribute type of the ES is as defined in ESS (RFC 2634 [5]).
     * When used to indicate the precise format of the data to be presented to the user, the following rules apply:
     *  the contentType indicates the type of the associated content. It is an object identifier (i.e. a unique string of
     * integers) assigned by an authority that defines the content type; and
     *  when the contentType is id-data the contentDescription shall define the presentation format; the
     * format may be defined by MIME types.
     * When the format of the content is defined by MIME types, the following rules apply:
     *  the contentType shall be id-data as defined in CMS (RFC 3852 [4]);
     *  the contentDescription shall be used to indicate the encoding of the data, in accordance with the rules
     * defined RFC 2045 [6]; see annex F for an example of structured contents and MIME.
     * NOTE 1: id-data OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs7(7) 1 }.
     * NOTE 2: contentDescription is optional in ESS (RFC 2634 [5]). It may be used to complement
     * contentTypes defined elsewhere; such definitions are outside the scope of the present document.
     *
     * @param parameters
     * @param signedAttributes
     * @return
     */
    private void addContentHints(final SignatureParameters parameters, final ASN1EncodableVector signedAttributes) {

        final BLevelParameters bLevelParameters = parameters.bLevel();
        if (DSSUtils.isNotBlank(bLevelParameters.getContentHintsType())) {

            final ASN1ObjectIdentifier contentHintsType = new ASN1ObjectIdentifier(
                    bLevelParameters.getContentHintsType());
            final String contentHintsDescriptionString = bLevelParameters.getContentHintsDescription();
            final DERUTF8String contentHintsDescription = DSSUtils.isBlank(contentHintsDescriptionString) ? null
                    : new DERUTF8String(contentHintsDescriptionString);
            //      "text/plain";
            //      "1.2.840.113549.1.7.1";

            final ContentHints contentHints = new ContentHints(contentHintsType, contentHintsDescription);
            final DERSet attrValues = new DERSet(contentHints);
            final Attribute attribute = new Attribute(PKCSObjectIdentifiers.id_aa_contentHint, attrValues);
            signedAttributes.add(attribute);
        }
    }

    /**
     * ETSI TS 101 733 V2.2.1 (2013-04)
     * <p/>
     * 5.10.2 content-identifier Attribute
     * The content-identifier attribute provides an identifier for the signed content, for use when a reference may be
     * later required to that content; for example, in the content-reference attribute in other signed data sent later. The
     * content-identifier shall be a signed attribute. content-identifier attribute type values for the ES have an ASN.1 type ContentIdentifier, as defined in
     * ESS (RFC 2634 [5]).
     * <p/>
     * The minimal content-identifier attribute should contain a concatenation of user-specific identification
     * information (such as a user name or public keying material identification information), a GeneralizedTime string,
     * and a random number.
     *
     * @param parameters
     * @param signedAttributes
     */
    private void addContentIdentifier(final SignatureParameters parameters,
            final ASN1EncodableVector signedAttributes) {

        /* this attribute is prohibited in PAdES B */
        if (!padesUsage) {

            final BLevelParameters bLevelParameters = parameters.bLevel();
            final String contentIdentifierPrefix = bLevelParameters.getContentIdentifierPrefix();
            if (DSSUtils.isNotBlank(contentIdentifierPrefix)) {

                final String contentIdentifierSuffix;
                if (DSSUtils.isBlank(bLevelParameters.getContentIdentifierSuffix())) {

                    final Date now = new Date();
                    final String asn1GeneralizedTimeString = new ASN1GeneralizedTime(now).getTimeString();
                    final long randomNumber = new Random(now.getTime()).nextLong();
                    contentIdentifierSuffix = asn1GeneralizedTimeString + randomNumber;
                    bLevelParameters.setContentIdentifierSuffix(contentIdentifierSuffix);
                } else {
                    contentIdentifierSuffix = bLevelParameters.getContentIdentifierSuffix();
                }
                final String contentIdentifierString = contentIdentifierPrefix + contentIdentifierSuffix;
                final ContentIdentifier contentIdentifier = new ContentIdentifier(
                        contentIdentifierString.getBytes());
                final DERSet attrValues = new DERSet(contentIdentifier);
                final Attribute attribute = new Attribute(PKCSObjectIdentifiers.id_aa_contentIdentifier,
                        attrValues);
                signedAttributes.add(attribute);
            }
        }
    }

    private void addSignaturePolicyId(final SignatureParameters parameters,
            final ASN1EncodableVector signedAttributes) {

        Policy policy = parameters.bLevel().getSignaturePolicy();
        if (policy != null && policy.getId() != null) {

            final String policyId = policy.getId();
            SignaturePolicyIdentifier sigPolicy = null;
            if (!"".equals(policyId)) { // explicit

                final ASN1ObjectIdentifier derOIPolicyId = new ASN1ObjectIdentifier(policyId);
                final ASN1ObjectIdentifier oid = policy.getDigestAlgorithm().getOid();
                final AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(oid);
                OtherHashAlgAndValue otherHashAlgAndValue = new OtherHashAlgAndValue(algorithmIdentifier,
                        new DEROctetString(policy.getDigestValue()));
                sigPolicy = new SignaturePolicyIdentifier(
                        new SignaturePolicyId(derOIPolicyId, otherHashAlgAndValue));
            } else {// implicit
                sigPolicy = new SignaturePolicyIdentifier();
            }
            final DERSet attrValues = new DERSet(sigPolicy);
            final Attribute attribute = new Attribute(PKCSObjectIdentifiers.id_aa_ets_sigPolicyId, attrValues);
            signedAttributes.add(attribute);
        }
    }

    private void addSigningCertificateAttribute(final SignatureParameters parameters,
            final ASN1EncodableVector signedAttributes) throws DSSException {

        final DigestAlgorithm digestAlgorithm = parameters.getDigestAlgorithm();
        final X509Certificate signingCertificate = parameters.getSigningCertificate();
        final byte[] encoded = DSSUtils.getEncoded(signingCertificate);
        final byte[] certHash = DSSUtils.digest(digestAlgorithm, encoded);
        if (LOG.isDebugEnabled()) {
            LOG.debug("Adding Certificate Hash {} with algorithm {}", DSSUtils.encodeHexString(certHash),
                    digestAlgorithm.getName());
        }
        final IssuerSerial issuerSerial = DSSUtils.getIssuerSerial(signingCertificate);
        if (digestAlgorithm == DigestAlgorithm.SHA1) {

            final ESSCertID essCertId = new ESSCertID(certHash, issuerSerial);
            final SigningCertificate cadesSigningCertificate = new SigningCertificate(essCertId);
            final DERSet attrValues = new DERSet(cadesSigningCertificate);
            final Attribute attribute = new Attribute(PKCSObjectIdentifiers.id_aa_signingCertificate, attrValues);
            signedAttributes.add(attribute);
        } else {

            final ESSCertIDv2 essCertIDv2 = new ESSCertIDv2(digestAlgorithm.getAlgorithmIdentifier(), certHash,
                    issuerSerial);
            final ESSCertIDv2[] essCertIDv2Array = new ESSCertIDv2[] { essCertIDv2 };
            final SigningCertificateV2 cadesSigningCertificateV2 = new SigningCertificateV2(essCertIDv2Array);
            final DERSet attrValues = new DERSet(cadesSigningCertificateV2);
            final Attribute attribute = new Attribute(PKCSObjectIdentifiers.id_aa_signingCertificateV2, attrValues);
            signedAttributes.add(attribute);
        }
    }
}