/*
* 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.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.ChainCertificate;
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;
import static eu.europa.ec.markt.dss.DigestAlgorithm.SHA1;
import static org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers.id_aa_contentHint;
import static org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers.id_aa_contentIdentifier;
import static org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers.id_aa_ets_commitmentType;
import static org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers.id_aa_ets_contentTimestamp;
import static org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers.id_aa_ets_sigPolicyId;
import static org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers.id_aa_ets_signerAttr;
import static org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers.id_aa_ets_signerLocation;
import static org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers.id_aa_signingCertificate;
import static org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers.id_aa_signingCertificateV2;
import static org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers.pkcs_9_at_signingTime;
/**
* 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(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(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(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(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(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(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(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(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 List<ChainCertificate> chainCertificateList = parameters.getCertificateChain();
final List<ASN1Encodable> signingCertificates = new ArrayList<ASN1Encodable>();
for (final ChainCertificate chainCertificate : chainCertificateList) {
if (!chainCertificate.isSignedAttribute()) {
continue;
}
final X509Certificate signingCertificate = chainCertificate.getX509Certificate();
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);
ASN1Encodable asn1Encodable;
if (digestAlgorithm == SHA1) {
final ESSCertID essCertID = new ESSCertID(certHash, issuerSerial);
asn1Encodable = new SigningCertificate(essCertID);
} else {
asn1Encodable = new ESSCertIDv2(digestAlgorithm.getAlgorithmIdentifier(), certHash, issuerSerial);
}
signingCertificates.add(asn1Encodable);
}
final Attribute attribute = createSigningCertificateAttributes(digestAlgorithm, signingCertificates);
signedAttributes.add(attribute);
}
private Attribute createSigningCertificateAttributes(final DigestAlgorithm digestAlgorithm, final List<ASN1Encodable> signingCertificates) {
final Attribute attribute;
if (digestAlgorithm == SHA1) {
final SigningCertificate[] signingCertificatesV1s = signingCertificates.toArray(new SigningCertificate[0]);
final DERSet derSet = new DERSet(signingCertificatesV1s);
attribute = new Attribute(id_aa_signingCertificate, derSet);
} else {
final ESSCertIDv2[] essCertIDv2s = signingCertificates.toArray(new ESSCertIDv2[0]);
final SigningCertificateV2 signingCertificateV2 = new SigningCertificateV2(essCertIDv2s);
final DERSet derSet = new DERSet(signingCertificateV2);
attribute = new Attribute(id_aa_signingCertificateV2, derSet);
}
return attribute;
}
}