All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.itextpdf.signatures.PdfPadesSigner Maven / Gradle / Ivy

There is a newer version: 9.0.0
Show newest version
/*
    This file is part of the iText (R) project.
    Copyright (c) 1998-2024 Apryse Group NV
    Authors: Apryse Software.

    This program is offered under a commercial and under the AGPL license.
    For commercial licensing, contact us at https://itextpdf.com/sales.  For AGPL licensing, see below.

    AGPL licensing:
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program 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 Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see .
 */
package com.itextpdf.signatures;

import com.itextpdf.bouncycastleconnector.BouncyCastleFactoryCreator;
import com.itextpdf.commons.bouncycastle.IBouncyCastleFactory;
import com.itextpdf.commons.utils.FileUtil;
import com.itextpdf.commons.utils.MessageFormatUtil;
import com.itextpdf.forms.PdfSigFieldLock;
import com.itextpdf.kernel.exceptions.PdfException;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.StampingProperties;
import com.itextpdf.signatures.LtvVerification.CertificateOption;
import com.itextpdf.signatures.LtvVerification.Level;
import com.itextpdf.signatures.PdfSigner.CryptoStandard;
import com.itextpdf.signatures.exceptions.SignExceptionMessageConstant;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * This class performs signing with PaDES related profiles using provided parameters.
 */
public class PdfPadesSigner {
    private static final String TEMP_FILE_NAME = "tempPdfFile";
    private static final IBouncyCastleFactory FACTORY = BouncyCastleFactoryCreator.getFactory();
    private static final String DEFAULT_DIGEST_ALGORITHM = DigestAlgorithms.SHA512;
    private static final Object LOCK_OBJECT = new Object();
    private static long increment = 0;
    
    private IOcspClient ocspClient = null;
    private ICrlClient crlClient;
    private IIssuingCertificateRetriever issuingCertificateRetriever = new IssuingCertificateRetriever();
    private int estimatedSize = 0;
    private String timestampSignatureName;
    private String temporaryDirectoryPath = null;
    private AccessPermissions accessPermissions = AccessPermissions.UNSPECIFIED;
    private PdfSigFieldLock fieldLock = null;
    private IExternalDigest externalDigest = new BouncyCastleDigest();
    private StampingProperties stampingProperties = new StampingProperties().useAppendMode();
    private StampingProperties stampingPropertiesWithMetaInfo = (StampingProperties) new StampingProperties()
            .useAppendMode().setEventCountingMetaInfo(new SignMetaInfo());

    private ByteArrayOutputStream tempOutputStream;
    private File tempFile;
    private final Set tempFiles = new HashSet<>();

    private final PdfReader reader;
    private final OutputStream outputStream;

    /**
     * Create an instance of PdfPadesSigner class. One instance shall be used for one signing operation.
     * 
     * @param reader {@link PdfReader} instance to read original PDF file
     * @param outputStream {@link OutputStream} output stream to write the resulting PDF file into
     */
    public PdfPadesSigner(PdfReader reader, OutputStream outputStream) {
        this.reader = reader;
        this.outputStream = outputStream;
    }

    /**
     * Sign the document provided in {@link PdfSigner} instance with PaDES Baseline-B Profile.
     * 
     * @param signerProperties {@link SignerProperties} properties to be used for main signing operation
     * @param chain the chain of certificates to be used for signing operation
     * @param externalSignature {@link IExternalSignature} instance to be used for main signing operation
     * 
     * @throws GeneralSecurityException in case of signing related exceptions
     * @throws IOException in case of files related exceptions
     */
    public void signWithBaselineBProfile(SignerProperties signerProperties, Certificate[] chain,
           IExternalSignature externalSignature) throws GeneralSecurityException, IOException {
        performSignDetached(signerProperties, true, externalSignature, chain, null);
    }

    /**
     * Sign the document provided in {@link PdfSigner} instance with PaDES Baseline-B Profile.
     *
     * @param signerProperties {@link SignerProperties} properties to be used for main signing operation
     * @param chain the chain of certificates to be used for signing operation
     * @param privateKey {@link PrivateKey} instance to be used for main signing operation
     *
     * @throws GeneralSecurityException in case of signing related exceptions
     * @throws IOException in case of files related exceptions
     */
    public void signWithBaselineBProfile(SignerProperties signerProperties, Certificate[] chain, PrivateKey privateKey)
            throws GeneralSecurityException, IOException {
        IExternalSignature externalSignature =
                new PrivateKeySignature(privateKey, getDigestAlgorithm(privateKey), FACTORY.getProviderName());
        signWithBaselineBProfile(signerProperties, chain, externalSignature);
    }

    /**
     * Sign the document provided in {@link PdfSigner} instance with PaDES Baseline-T Profile.
     *
     * @param signerProperties {@link SignerProperties} properties to be used for main signing operation
     * @param chain the chain of certificates to be used for signing operation
     * @param externalSignature {@link IExternalSignature} instance to be used for main signing operation
     * @param tsaClient {@link ITSAClient} instance to be used for timestamp creation
     *
     * @throws GeneralSecurityException in case of signing related exceptions
     * @throws IOException in case of files related exceptions
     */
    public void signWithBaselineTProfile(SignerProperties signerProperties, Certificate[] chain,
            IExternalSignature externalSignature, ITSAClient tsaClient) throws GeneralSecurityException, IOException {
        performSignDetached(signerProperties, true, externalSignature, chain, tsaClient);
    }

    /**
     * Sign the document provided in {@link PdfSigner} instance with PaDES Baseline-T Profile.
     *
     * @param signerProperties {@link SignerProperties} properties to be used for main signing operation
     * @param chain the chain of certificates to be used for signing operation
     * @param privateKey {@link PrivateKey} instance to be used for main signing operation
     * @param tsaClient {@link ITSAClient} instance to be used for timestamp creation
     *
     * @throws GeneralSecurityException in case of signing related exceptions
     * @throws IOException in case of files related exceptions
     */
    public void signWithBaselineTProfile(SignerProperties signerProperties, Certificate[] chain, PrivateKey privateKey,
            ITSAClient tsaClient) throws GeneralSecurityException, IOException {
        IExternalSignature externalSignature =
                new PrivateKeySignature(privateKey, getDigestAlgorithm(privateKey), FACTORY.getProviderName());
        signWithBaselineTProfile(signerProperties, chain, externalSignature, tsaClient);
    }

    /**
     * Sign the document provided in {@link PdfSigner} instance with PaDES Baseline-LT Profile.
     *
     * @param signerProperties {@link SignerProperties} properties to be used for main signing operation
     * @param chain the chain of certificates to be used for signing operation
     * @param externalSignature {@link IExternalSignature} instance to be used for main signing operation
     * @param tsaClient {@link ITSAClient} instance to be used for timestamp creation
     *
     * @throws GeneralSecurityException in case of signing related exceptions
     * @throws IOException in case of files related exceptions
     */
    public void signWithBaselineLTProfile(SignerProperties signerProperties, Certificate[] chain,
            IExternalSignature externalSignature, ITSAClient tsaClient) throws GeneralSecurityException, IOException {
        createRevocationClients(chain[0], true);
        try {
            performSignDetached(signerProperties, false, externalSignature, chain, tsaClient);
            try (InputStream inputStream = createInputStream();
                    PdfDocument pdfDocument = new PdfDocument(new PdfReader(inputStream),
                            new PdfWriter(outputStream), stampingPropertiesWithMetaInfo)) {
                performLtvVerification(pdfDocument, Collections.singletonList(signerProperties.getFieldName()),
                        LtvVerification.RevocationDataNecessity.REQUIRED_FOR_SIGNING_CERTIFICATE);
            }
        } finally {
            deleteTempFiles();
        }
    }

    /**
     * Sign the document provided in {@link PdfSigner} instance with PaDES Baseline-LT Profile.
     *
     * @param signerProperties {@link SignerProperties} properties to be used for main signing operation
     * @param chain the chain of certificates to be used for signing operation
     * @param privateKey {@link PrivateKey} instance to be used for main signing operation
     * @param tsaClient {@link ITSAClient} instance to be used for timestamp creation
     *
     * @throws GeneralSecurityException in case of signing related exceptions
     * @throws IOException in case of files related exceptions
     */
    public void signWithBaselineLTProfile(SignerProperties signerProperties, Certificate[] chain, PrivateKey privateKey,
            ITSAClient tsaClient) throws GeneralSecurityException, IOException {
        IExternalSignature externalSignature =
                new PrivateKeySignature(privateKey, getDigestAlgorithm(privateKey), FACTORY.getProviderName());
        signWithBaselineLTProfile(signerProperties, chain, externalSignature, tsaClient);
    }

    /**
     * Sign the document provided in {@link PdfSigner} instance with PaDES Baseline-LTA Profile.
     *
     * @param signerProperties {@link SignerProperties} properties to be used for main signing operation
     * @param chain the chain of certificates to be used for signing operation
     * @param externalSignature {@link IExternalSignature} instance to be used for main signing operation
     * @param tsaClient {@link ITSAClient} instance to be used for timestamp creation
     *
     * @throws GeneralSecurityException in case of signing related exceptions
     * @throws IOException in case of files related exceptions
     */
    public void signWithBaselineLTAProfile(SignerProperties signerProperties, Certificate[] chain,
            IExternalSignature externalSignature, ITSAClient tsaClient) throws IOException, GeneralSecurityException {
        createRevocationClients(chain[0], true);
        try {
            performSignDetached(signerProperties, false, externalSignature, chain, tsaClient);
            try (InputStream inputStream = createInputStream();
                    PdfDocument pdfDocument = new PdfDocument(new PdfReader(inputStream),
                            new PdfWriter(createOutputStream()), stampingPropertiesWithMetaInfo)) {
                performLtvVerification(pdfDocument, Collections.singletonList(signerProperties.getFieldName()),
                        LtvVerification.RevocationDataNecessity.REQUIRED_FOR_SIGNING_CERTIFICATE);
                performTimestamping(pdfDocument, outputStream, tsaClient);
            }
        } finally {
            deleteTempFiles();
        }
    }

    /**
     * Sign the document provided in {@link PdfSigner} instance with PaDES Baseline-LTA Profile.
     *
     * @param signerProperties {@link SignerProperties} properties to be used for main signing operation
     * @param chain the chain of certificates to be used for signing operation
     * @param privateKey {@link PrivateKey} instance to be used for main signing operation
     * @param tsaClient {@link ITSAClient} instance to be used for timestamp creation
     *
     * @throws GeneralSecurityException in case of signing related exceptions
     * @throws IOException in case of files related exceptions
     */
    public void signWithBaselineLTAProfile(SignerProperties signerProperties, Certificate[] chain,
            PrivateKey privateKey, ITSAClient tsaClient) throws GeneralSecurityException, IOException {
        IExternalSignature externalSignature =
                new PrivateKeySignature(privateKey, getDigestAlgorithm(privateKey), FACTORY.getProviderName());
        signWithBaselineLTAProfile(signerProperties, chain, externalSignature, tsaClient);
    }

    /**
     * Add revocation information for all the signatures which could be found in the provided document.
     * Also add timestamp signature on top of that.
     *
     * @param tsaClient {@link ITSAClient} TSA Client to be used for timestamp signature creation
     * 
     * @throws IOException in case of files related exceptions
     * @throws GeneralSecurityException in case of signing related exceptions
     */
    public void prolongSignatures(ITSAClient tsaClient)
            throws IOException, GeneralSecurityException {
        OutputStream documentOutputStream = tsaClient == null ? outputStream : createOutputStream();
        try (PdfDocument pdfDocument = new PdfDocument(reader, new PdfWriter(documentOutputStream),
                stampingProperties)) {
            SignatureUtil signatureUtil = new SignatureUtil(pdfDocument);
            List signatureNames = signatureUtil.getSignatureNames();
            if (signatureNames.isEmpty()) {
                throw new PdfException(SignExceptionMessageConstant.NO_SIGNATURES_TO_PROLONG);
            }
            createRevocationClients(null, false);
            performLtvVerification(pdfDocument, signatureNames, LtvVerification.RevocationDataNecessity.OPTIONAL);
            if (tsaClient != null) {
                performTimestamping(pdfDocument, outputStream, tsaClient);
            }
        }
    }

    /**
     * Add revocation information for all the signatures which could be found in the provided document.
     *
     * @throws IOException in case of files related exceptions
     * @throws GeneralSecurityException in case of signing related exceptions
     */
    public void prolongSignatures()
            throws IOException, GeneralSecurityException {
        prolongSignatures(null);
    }

    /**
     * Set temporary directory to be used for temporary files creation.
     * 

* If none is set, temporary documents will be created in memory. * * @param temporaryDirectoryPath {@link String} representing relative or absolute path to the directory * * @return same instance of {@link PdfPadesSigner} */ public PdfPadesSigner setTemporaryDirectoryPath(String temporaryDirectoryPath) { this.temporaryDirectoryPath = temporaryDirectoryPath; return this; } /** * Set certification level which specifies DocMDP level which is expected to be set. * * @param accessPermissions {@link AccessPermissions} certification level * * @return same instance of {@link PdfPadesSigner} */ public PdfPadesSigner setCertificationLevel(AccessPermissions accessPermissions) { this.accessPermissions = accessPermissions; return this; } /** * Set FieldMDP rules to be applied for this signature. * * @param fieldLock {@link PdfSigFieldLock} field lock dictionary. * * @return same instance of {@link PdfPadesSigner} */ public PdfPadesSigner setSignatureFieldLock(PdfSigFieldLock fieldLock) { this.fieldLock = fieldLock; return this; } /** * Set the name to be used for timestamp signature creation. *

* This setter is only relevant if * {@link PdfPadesSigner#signWithBaselineLTAProfile} or {@link PdfPadesSigner#prolongSignatures} methods are used. *

* If none is set, randomly generated signature name will be used. * * @param timestampSignatureName {@link String} representing the name of a timestamp signature to be applied * * @return same instance of {@link PdfPadesSigner} */ public PdfPadesSigner setTimestampSignatureName(String timestampSignatureName) { this.timestampSignatureName = timestampSignatureName; return this; } /** * Set stamping properties to be used during main signing operation. *

* If none is set, stamping properties with append mode enabled will be used * * @param stampingProperties {@link StampingProperties} instance to be used during main signing operation * * @return same instance of {@link PdfPadesSigner} */ public PdfPadesSigner setStampingProperties(StampingProperties stampingProperties) { this.stampingProperties = stampingProperties; if (stampingProperties.isEventCountingMetaInfoSet()) { this.stampingPropertiesWithMetaInfo = stampingProperties; } return this; } /** * Set estimated size of a signature to be applied. *

* This parameter represents estimated amount of bytes to be preserved for the signature. *

* If none is set, 0 will be used and the required space will be calculated during the signing. * * @param estimatedSize amount of bytes to be used as estimated value * * @return same instance of {@link PdfPadesSigner} */ public PdfPadesSigner setEstimatedSize(int estimatedSize) { this.estimatedSize = estimatedSize; return this; } /** * Set {@link IOcspClient} to be used for LTV Verification. *

* This setter is only relevant if Baseline-LT Profile level or higher is used. *

* If none is set, there will be an attempt to create default OCSP Client instance using the certificate chain. * * @param ocspClient {@link IOcspClient} instance to be used for LTV Verification * * @return same instance of {@link PdfPadesSigner} */ public PdfPadesSigner setOcspClient(IOcspClient ocspClient) { this.ocspClient = ocspClient; return this; } /** * Set {@link ICrlClient} to be used for LTV Verification. *

* This setter is only relevant if Baseline-LT Profile level or higher is used. *

* If none is set, there will be an attempt to create default CRL Client instance using the certificate chain. * * @param crlClient {@link ICrlClient} instance to be used for LTV Verification * * @return same instance of {@link PdfPadesSigner} */ public PdfPadesSigner setCrlClient(ICrlClient crlClient) { this.crlClient = crlClient; return this; } /** * Set {@link IExternalDigest} to be used for main signing operation. *

* If none is set, {@link BouncyCastleDigest} instance will be used instead. * * @param externalDigest {@link IExternalDigest} to be used for main signing operation. * * @return same instance of {@link PdfPadesSigner} */ public PdfPadesSigner setExternalDigest(IExternalDigest externalDigest) { this.externalDigest = externalDigest; return this; } /** * Set {@link IIssuingCertificateRetriever} to be used before main signing operation. * *

* If none is set, {@link IssuingCertificateRetriever} instance will be used instead. * * @param issuingCertificateRetriever {@link IIssuingCertificateRetriever} instance to be used for getting missing * certificates in chain or CRL response issuer certificates. * * @return same instance of {@link PdfPadesSigner}. */ public PdfPadesSigner setIssuingCertificateRetriever(IIssuingCertificateRetriever issuingCertificateRetriever) { this.issuingCertificateRetriever = issuingCertificateRetriever; return this; } /** * Set certificate list to be used by the {@link IIssuingCertificateRetriever} to retrieve missing certificates. * * @param certificateList certificate list for getting missing certificates in chain * or CRL response issuer certificates. * * @return same instance of {@link PdfPadesSigner}. */ public PdfPadesSigner setTrustedCertificates(List certificateList) { this.issuingCertificateRetriever.setTrustedCertificates(certificateList); return this; } void performTimestamping(PdfDocument document, OutputStream outputStream, ITSAClient tsaClient) throws IOException, GeneralSecurityException { PdfSigner timestampSigner = new PdfSigner(document, outputStream, tempOutputStream, tempFile); timestampSigner.timestamp(tsaClient, timestampSignatureName); } PdfSigner createPdfSigner(SignerProperties signerProperties, boolean isFinal) throws IOException { String tempFilePath = null; if (temporaryDirectoryPath != null) { tempFilePath = getNextTempFile().getAbsolutePath(); } return new PdfSigner(reader, isFinal ? outputStream : createOutputStream(), tempFilePath, stampingProperties, signerProperties); } void performLtvVerification(PdfDocument pdfDocument, List signatureNames, LtvVerification.RevocationDataNecessity revocationDataNecessity) throws IOException, GeneralSecurityException { LtvVerification ltvVerification = new LtvVerification(pdfDocument) .setRevocationDataNecessity(revocationDataNecessity) .setIssuingCertificateRetriever(issuingCertificateRetriever); for (String signatureName : signatureNames) { ltvVerification.addVerification(signatureName, ocspClient, crlClient, CertificateOption.ALL_CERTIFICATES, Level.OCSP_OPTIONAL_CRL, LtvVerification.CertificateInclusion.YES); } ltvVerification.merge(); } void deleteTempFiles() { for (File tempFile : tempFiles) { tempFile.delete(); } } OutputStream createOutputStream() throws FileNotFoundException { if (temporaryDirectoryPath != null) { return FileUtil.getFileOutputStream(getNextTempFile()); } tempOutputStream = new ByteArrayOutputStream(); return tempOutputStream; } InputStream createInputStream() throws IOException { if (temporaryDirectoryPath != null) { return FileUtil.getInputStreamForFile(tempFile); } return new ByteArrayInputStream(tempOutputStream.toByteArray()); } void createRevocationClients(Certificate signingCert, boolean clientsRequired) { if (crlClient == null && ocspClient == null && clientsRequired) { X509Certificate signingCertificate = (X509Certificate) signingCert; if (CertificateUtil.getOCSPURL(signingCertificate) == null && CertificateUtil.getCRLURLs(signingCertificate).isEmpty()) { throw new PdfException(SignExceptionMessageConstant.DEFAULT_CLIENTS_CANNOT_BE_CREATED); } } if (crlClient == null) { crlClient = new CrlClientOnline(); } if (ocspClient == null) { ocspClient = new OcspClientBouncyCastle(); } } private void performSignDetached(SignerProperties signerProperties, boolean isFinal, IExternalSignature externalSignature, Certificate[] chain, ITSAClient tsaClient) throws GeneralSecurityException, IOException { Certificate[] fullChain = issuingCertificateRetriever.retrieveMissingCertificates(chain); PdfSigner signer = createPdfSigner(signerProperties, isFinal); signer.setCertificationLevel(accessPermissions); signer.setFieldLockDict(fieldLock); try { signer.signDetached(externalDigest, externalSignature, fullChain, null, null, tsaClient, estimatedSize, CryptoStandard.CADES); } finally { signer.originalOS.close(); } } private File getNextTempFile() { if (!FileUtil.directoryExists(temporaryDirectoryPath)) { throw new PdfException(MessageFormatUtil.format(SignExceptionMessageConstant.PATH_IS_NOT_DIRECTORY, temporaryDirectoryPath)); } synchronized (LOCK_OBJECT) { do { increment++; tempFile = new File(temporaryDirectoryPath + "/" + TEMP_FILE_NAME + increment + ".pdf"); } while (tempFile.exists()); tempFiles.add(tempFile); } return tempFile; } private String getDigestAlgorithm(PrivateKey privateKey) { String signatureAlgorithm = SignUtils.getPrivateKeyAlgorithm(privateKey); switch (signatureAlgorithm) { case "Ed25519": return DigestAlgorithms.SHA512; case "Ed448": return DigestAlgorithms.SHAKE256; default: return DEFAULT_DIGEST_ALGORITHM; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy