/*
* 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.validation102853.pades;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import eu.europa.ec.markt.dss.DSSUtils;
import eu.europa.ec.markt.dss.DigestAlgorithm;
import eu.europa.ec.markt.dss.EncryptionAlgorithm;
import eu.europa.ec.markt.dss.exception.DSSException;
import eu.europa.ec.markt.dss.signature.DSSDocument;
import eu.europa.ec.markt.dss.signature.SignatureLevel;
import eu.europa.ec.markt.dss.signature.pdf.PdfDocTimestampInfo;
import eu.europa.ec.markt.dss.signature.pdf.PdfSignatureInfo;
import eu.europa.ec.markt.dss.signature.pdf.PdfSignatureOrDocTimestampInfo;
import eu.europa.ec.markt.dss.signature.pdf.pdfbox.PdfDssDict;
import eu.europa.ec.markt.dss.validation102853.AdvancedSignature;
import eu.europa.ec.markt.dss.validation102853.CAdESCertificateSource;
import eu.europa.ec.markt.dss.validation102853.CertificatePool;
import eu.europa.ec.markt.dss.validation102853.CertificateToken;
import eu.europa.ec.markt.dss.validation102853.DefaultAdvancedSignature;
import eu.europa.ec.markt.dss.validation102853.SignatureForm;
import eu.europa.ec.markt.dss.validation102853.SignaturePolicy;
import eu.europa.ec.markt.dss.validation102853.TimestampReference;
import eu.europa.ec.markt.dss.validation102853.TimestampToken;
import eu.europa.ec.markt.dss.validation102853.TimestampType;
import eu.europa.ec.markt.dss.validation102853.bean.CandidatesForSigningCertificate;
import eu.europa.ec.markt.dss.validation102853.bean.CertifiedRole;
import eu.europa.ec.markt.dss.validation102853.bean.CommitmentType;
import eu.europa.ec.markt.dss.validation102853.bean.SignatureCryptographicVerification;
import eu.europa.ec.markt.dss.validation102853.bean.SignatureProductionPlace;
import eu.europa.ec.markt.dss.validation102853.cades.CAdESSignature;
import eu.europa.ec.markt.dss.validation102853.certificate.CertificateRef;
import eu.europa.ec.markt.dss.validation102853.crl.CRLRef;
import eu.europa.ec.markt.dss.validation102853.crl.OfflineCRLSource;
import eu.europa.ec.markt.dss.validation102853.ocsp.OCSPRef;
import eu.europa.ec.markt.dss.validation102853.ocsp.OfflineOCSPSource;
/**
* Implementation of AdvancedSignature for PAdES
*
* @version $Revision: 1849 $ - $Date: 2013-04-04 17:51:32 +0200 (Thu, 04 Apr 2013) $
*/
public class PAdESSignature extends DefaultAdvancedSignature {
private static final Logger LOG = LoggerFactory.getLogger(PAdESSignature.class);
private final DSSDocument document;
private final PdfDssDict pdfCatalog;
private final PdfDssDict outerCatalog;
private final CAdESSignature cadesSignature;
private final List<TimestampToken> cadesTimestamps;
private final List<TimestampToken> cadesArchiveTimestamps;
private final PdfSignatureInfo pdfSignatureInfo;
private PAdESCertificateSource padesCertSources;
/**
* This list represents all digest algorithms used to calculate the digest values of certificates.
*/
private Set<DigestAlgorithm> usedCertificatesDigestAlgorithms = new HashSet<DigestAlgorithm>();
/**
* The default constructor for PAdESSignature.
*
* @param document
* @param pdfSignatureInfo
* @param certPool
* @throws DSSException
*/
protected PAdESSignature(final DSSDocument document, final PdfSignatureInfo pdfSignatureInfo, final CertificatePool certPool) throws DSSException {
super(certPool);
this.document = document;
this.pdfCatalog = pdfSignatureInfo.getDocumentDictionary();
this.outerCatalog = pdfSignatureInfo.getOuterCatalog();
this.cadesSignature = pdfSignatureInfo.getCades();
this.cadesTimestamps = cadesSignature.getSignatureTimestamps();
this.cadesArchiveTimestamps = cadesSignature.getArchiveTimestamps();
this.pdfSignatureInfo = pdfSignatureInfo;
cadesSignature.setPadesSigningTime(getSigningTime());
}
@Override
public SignatureForm getSignatureForm() {
return SignatureForm.PAdES;
}
@Override
public EncryptionAlgorithm getEncryptionAlgorithm() {
return cadesSignature.getEncryptionAlgorithm();
}
@Override
public DigestAlgorithm getDigestAlgorithm() {
return cadesSignature.getDigestAlgorithm();
}
@Override
public PAdESCertificateSource getCertificateSource() {
if (padesCertSources == null) {
final CAdESCertificateSource cadesCertSource = cadesSignature.getCertificateSource();
padesCertSources = new PAdESCertificateSource(getDSSDictionary(), cadesCertSource, certPool);
}
return padesCertSources;
}
private PdfDssDict getDSSDictionary() {
PdfDssDict catalog = outerCatalog != null ? outerCatalog : pdfCatalog;
return catalog;
}
@Override
public OfflineCRLSource getCRLSource() {
if (offlineCRLSource == null) {
final PdfDssDict dssDictionary = getDSSDictionary();
offlineCRLSource = new PAdESCRLSource(cadesSignature, dssDictionary);
}
return offlineCRLSource;
}
@Override
public OfflineOCSPSource getOCSPSource() {
if (offlineOCSPSource == null) {
final PdfDssDict dssDictionary = getDSSDictionary();
offlineOCSPSource = new PAdESOCSPSource(cadesSignature, dssDictionary);
}
return offlineOCSPSource;
}
@Override
public CandidatesForSigningCertificate getCandidatesForSigningCertificate() {
return cadesSignature.getCandidatesForSigningCertificate();
}
@Override
public Date getSigningTime() {
if (pdfSignatureInfo.getSigningDate() != null) {
return pdfSignatureInfo.getSigningDate();
}
return null;
}
@Override
public SignaturePolicy getPolicyId() {
return cadesSignature.getPolicyId();
}
@Override
public SignatureProductionPlace getSignatureProductionPlace() {
String location = pdfSignatureInfo.getLocation();
if (location == null || location.trim().length() == 0) {
return cadesSignature.getSignatureProductionPlace();
} else {
SignatureProductionPlace signatureProductionPlace = new SignatureProductionPlace();
signatureProductionPlace.setCountryName(location);
return signatureProductionPlace;
}
}
@Override
public String getContentType() {
return "application/pdf";
}
@Override
public String getContentIdentifier() {
return null;
}
@Override
public String getContentHints() {
return null;
}
@Override
public String[] getClaimedSignerRoles() {
return cadesSignature.getClaimedSignerRoles();
}
@Override
public List<CertifiedRole> getCertifiedSignerRoles() {
return null;
}
@Override
public List<TimestampToken> getContentTimestamps() {
final List<TimestampToken> contentTimestamps = cadesSignature.getContentTimestamps();
return contentTimestamps;
}
@Override
public byte[] getContentTimestampData(final TimestampToken timestampToken) {
final byte[] contentTimestampData = cadesSignature.getContentTimestampData(timestampToken);
return contentTimestampData;
}
@Override
public List<TimestampToken> getSignatureTimestamps() {
final List<TimestampToken> result = new ArrayList<TimestampToken>();
result.addAll(cadesTimestamps);
final Map<PdfSignatureOrDocTimestampInfo, Boolean> outerSignatures = pdfSignatureInfo.getOuterSignatures();
for (final PdfSignatureOrDocTimestampInfo outerSignature : outerSignatures.keySet()) {
if (outerSignature.isTimestamp() && (outerSignature instanceof PdfDocTimestampInfo)) {
final PdfDocTimestampInfo pdfBoxTimestampInfo = (PdfDocTimestampInfo) outerSignature;
// do not return this timestamp if it's an archive timestamp
final TimestampToken timestampToken = pdfBoxTimestampInfo.getTimestampToken();
if (timestampToken.getTimeStampType() == TimestampType.SIGNATURE_TIMESTAMP) {
timestampToken.setTimestampedReferences(cadesSignature.getSignatureTimestampedReferences());
result.add(timestampToken);
}
}
}
return Collections.unmodifiableList(result);
}
@Override
public List<TimestampToken> getTimestampsX1() {
/* Not applicable for PAdES */
return Collections.emptyList();
}
@Override
public List<TimestampToken> getTimestampsX2() {
/* Not applicable for PAdES */
return Collections.emptyList();
}
@Override
public List<TimestampToken> getArchiveTimestamps() {
final List<TimestampToken> archiveTimestampTokenList = new ArrayList<TimestampToken>();
archiveTimestampTokenList.addAll(cadesArchiveTimestamps); // (Bob) ???
final List<String> timestampedTimestamps = new ArrayList<String>();
final Map<PdfSignatureOrDocTimestampInfo, Boolean> outerSignatures = pdfSignatureInfo.getOuterSignatures();
usedCertificatesDigestAlgorithms.add(DigestAlgorithm.SHA1);
for (final PdfSignatureOrDocTimestampInfo outerSignature : outerSignatures.keySet()) {
if (!outerSignature.isTimestamp()) {
continue;
}
PdfDocTimestampInfo pdfBoxTimestampInfo = (PdfDocTimestampInfo) outerSignature;
// return this timestamp if it's an archive timestamp
final TimestampToken timestampToken = pdfBoxTimestampInfo.getTimestampToken();
if (timestampToken.getTimeStampType() == TimestampType.ARCHIVE_TIMESTAMP) {
final List<TimestampReference> references = cadesSignature.getSignatureTimestampedReferences();
for (final String timestampId : timestampedTimestamps) {
final TimestampReference signatureReference_ = new TimestampReference(timestampId);
references.add(signatureReference_);
}
final List<CertificateToken> certificates = getCertificates();
for (final CertificateToken certificate : certificates) {
final byte[] encodedCertificate = certificate.getEncoded();
final byte[] certificateDigest = DSSUtils.digest(DigestAlgorithm.SHA1, encodedCertificate);
final TimestampReference certificateTimestampReference = createCertificateTimestampReference(DigestAlgorithm.SHA1, certificateDigest);
references.add(certificateTimestampReference);
}
timestampToken.setTimestampedReferences(references);
archiveTimestampTokenList.add(timestampToken);
}
timestampedTimestamps.add(String.valueOf(timestampToken.getDSSId()));
}
return Collections.unmodifiableList(archiveTimestampTokenList);
}
private TimestampReference createCertificateTimestampReference(final DigestAlgorithm digestAlgorithm, final byte[] certHash) {
final TimestampReference reference = new TimestampReference(digestAlgorithm.name(), DSSUtils.base64Encode(certHash));
return reference;
}
@Override
public List<CertificateToken> getCertificates() {
return getCertificateSource().getCertificates();
}
@Override
public SignatureCryptographicVerification checkSignatureIntegrity() {
if (signatureCryptographicVerification != null) {
return signatureCryptographicVerification;
}
signatureCryptographicVerification = pdfSignatureInfo.checkIntegrity();
return signatureCryptographicVerification;
}
@Override
public void checkSigningCertificate() {
// TODO-Bob (13/07/2014):
}
@Override
public List<AdvancedSignature> getCounterSignatures() {
/* Not applicable for PAdES */
return Collections.emptyList();
}
@Override
public List<CertificateRef> getCertificateRefs() {
return cadesSignature.getCertificateRefs();
}
@Override
public List<CRLRef> getCRLRefs() {
return getCAdESSignature().getCRLRefs();
}
@Override
public List<OCSPRef> getOCSPRefs() {
return cadesSignature.getOCSPRefs();
}
@Override
public byte[] getSignatureTimestampData(final TimestampToken timestampToken, String canonicalizationMethod) {
if (cadesTimestamps.contains(timestampToken)) {
return cadesSignature.getSignatureTimestampData(timestampToken, null);
} else {
for (final PdfSignatureOrDocTimestampInfo signatureInfo : pdfSignatureInfo.getOuterSignatures().keySet()) {
if (signatureInfo instanceof PdfDocTimestampInfo) {
PdfDocTimestampInfo pdfTimestampInfo = (PdfDocTimestampInfo) signatureInfo;
if (pdfTimestampInfo.getTimestampToken().equals(timestampToken)) {
final byte[] signedDocumentBytes = pdfTimestampInfo.getSignedDocumentBytes();
return signedDocumentBytes;
}
}
}
}
throw new DSSException("Timestamp Data not found");
}
@Override
public byte[] getTimestampX1Data(final TimestampToken timestampToken, String canonicalizationMethod) {
/* Not applicable for PAdES */
return null;
}
@Override
public byte[] getTimestampX2Data(final TimestampToken timestampToken, String canonicalizationMethod) {
/* Not applicable for PAdES */
return null;
}
/**
* @return the CAdES signature underlying this PAdES signature
*/
public CAdESSignature getCAdESSignature() {
return cadesSignature;
}
@Override
public byte[] getArchiveTimestampData(TimestampToken timestampToken, String canonicalizationMethod) {
if (cadesArchiveTimestamps.contains(timestampToken)) {
return cadesSignature.getArchiveTimestampData(timestampToken, null);
} else {
for (final PdfSignatureOrDocTimestampInfo signatureInfo : pdfSignatureInfo.getOuterSignatures().keySet()) {
if (signatureInfo instanceof PdfDocTimestampInfo) {
PdfDocTimestampInfo pdfTimestampInfo = (PdfDocTimestampInfo) signatureInfo;
if (pdfTimestampInfo.getTimestampToken().equals(timestampToken)) {
final byte[] signedDocumentBytes = pdfTimestampInfo.getSignedDocumentBytes();
return signedDocumentBytes;
}
}
}
}
throw new DSSException("Timestamp Data not found");
}
@Override
public String getId() {
return cadesSignature.getId();
}
@Override
public List<TimestampReference> getTimestampedReferences() {
/* Not applicable for PAdES */
return Collections.emptyList();
}
@Override
public Set<DigestAlgorithm> getUsedCertificatesDigestAlgorithms() {
return usedCertificatesDigestAlgorithms;
}
public boolean isDataForSignatureLevelPresent(SignatureLevel signatureLevel) {
boolean dataForLevelPresent = true;
final List<TimestampToken> signatureTimestamps = getSignatureTimestamps();
switch (signatureLevel) {
case PAdES_BASELINE_LTA:
dataForLevelPresent = hasDocumentTimestampOnTopOfDSSDict();
dataForLevelPresent &= (((signatureTimestamps != null) && (!signatureTimestamps.isEmpty())));
break;
case PAdES_102778_LTV:
dataForLevelPresent = hasDocumentTimestampOnTopOfDSSDict();
break;
case PAdES_BASELINE_LT:
dataForLevelPresent &= hasDSSDictionary();
// break omitted purposely
case PAdES_BASELINE_T:
dataForLevelPresent &= (((signatureTimestamps != null) && (!signatureTimestamps.isEmpty())));
// break omitted purposely
case PAdES_BASELINE_B:
dataForLevelPresent &= (pdfSignatureInfo != null);
break;
default:
throw new IllegalArgumentException("Unknown level " + signatureLevel);
}
LOG.debug("Level {} found on document {} = {}", new Object[]{signatureLevel, document.getName(), dataForLevelPresent});
return dataForLevelPresent;
}
@Override
public SignatureLevel[] getSignatureLevels() {
return new SignatureLevel[]{SignatureLevel.PAdES_BASELINE_B, SignatureLevel.PAdES_BASELINE_T, SignatureLevel.PAdES_BASELINE_LT, SignatureLevel.PAdES_102778_LTV, SignatureLevel.PAdES_BASELINE_LTA};
}
private boolean hasDSSDictionary() {
for (final PdfSignatureOrDocTimestampInfo outerSignature : pdfSignatureInfo.getOuterSignatures().keySet()) {
if (outerSignature.getDocumentDictionary() != null) {
return true;
}
}
return false;
}
private boolean hasDocumentTimestampOnTopOfDSSDict() {
for (final PdfSignatureOrDocTimestampInfo outerSignature : pdfSignatureInfo.getOuterSignatures().keySet()) {
if (outerSignature.getDocumentDictionary() != null) {
if (outerSignature.isTimestamp()) {
return true;
}
}
}
return false;
}
@Override
public CommitmentType getCommitmentTypeIndication() {
return cadesSignature.getCommitmentTypeIndication();
}
public boolean hasOuterSignatures() {
return !pdfSignatureInfo.getOuterSignatures().isEmpty();
}
public PdfSignatureInfo getPdfSignatureInfo() {
return pdfSignatureInfo;
}
}