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

com.itextpdf.signatures.PdfSigner 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-2023 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.commons.bouncycastle.asn1.esf.ISignaturePolicyIdentifier;
import com.itextpdf.forms.PdfAcroForm;
import com.itextpdf.forms.PdfSigFieldLock;
import com.itextpdf.forms.fields.PdfFormCreator;
import com.itextpdf.forms.fields.PdfFormField;
import com.itextpdf.forms.fields.PdfSignatureFormField;
import com.itextpdf.forms.fields.SignatureFormFieldBuilder;
import com.itextpdf.forms.form.element.SignatureFieldAppearance;
import com.itextpdf.io.source.ByteBuffer;
import com.itextpdf.io.source.IRandomAccessSource;
import com.itextpdf.io.source.RASInputStream;
import com.itextpdf.io.source.RandomAccessSourceFactory;
import com.itextpdf.commons.utils.DateTimeUtil;
import com.itextpdf.commons.utils.FileUtil;
import com.itextpdf.io.util.StreamUtil;
import com.itextpdf.kernel.exceptions.PdfException;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.IsoKey;
import com.itextpdf.kernel.pdf.PdfArray;
import com.itextpdf.kernel.pdf.PdfDate;
import com.itextpdf.kernel.pdf.PdfDeveloperExtension;
import com.itextpdf.kernel.pdf.PdfDictionary;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfLiteral;
import com.itextpdf.kernel.pdf.PdfName;
import com.itextpdf.kernel.pdf.PdfNumber;
import com.itextpdf.kernel.pdf.PdfObject;
import com.itextpdf.kernel.pdf.PdfOutputStream;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.kernel.pdf.PdfString;
import com.itextpdf.kernel.pdf.PdfVersion;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.StampingProperties;
import com.itextpdf.kernel.pdf.annot.PdfAnnotation;
import com.itextpdf.kernel.pdf.annot.PdfWidgetAnnotation;
import com.itextpdf.pdfa.PdfAAgnosticPdfDocument;
import com.itextpdf.signatures.exceptions.SignExceptionMessageConstant;

import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Takes care of the cryptographic options and appearances that form a signature.
 */
public class PdfSigner {

    /**
     * Enum containing the Cryptographic Standards. Possible values are "CMS" and "CADES".
     */
    public enum CryptoStandard {
        /**
         * Cryptographic Message Syntax.
         */
        CMS,

        /**
         * CMS Advanced Electronic Signatures.
         */
        CADES
    }

    /**
     * Approval signature.
     */
    public static final int NOT_CERTIFIED = 0;

    /**
     * Author signature, no changes allowed.
     */
    public static final int CERTIFIED_NO_CHANGES_ALLOWED = 1;

    /**
     * Author signature, form filling allowed.
     */
    public static final int CERTIFIED_FORM_FILLING = 2;

    /**
     * Author signature, form filling and annotations allowed.
     */
    public static final int CERTIFIED_FORM_FILLING_AND_ANNOTATIONS = 3;

    /**
     * The certification level.
     */
    protected int certificationLevel = NOT_CERTIFIED;

    /**
     * The name of the field.
     */
    protected String fieldName;

    /**
     * The file right before the signature is added (can be null).
     */
    protected RandomAccessFile raf;

    /**
     * The bytes of the file right before the signature is added (if raf is null).
     */
    protected byte[] bout;

    /**
     * Array containing the byte positions of the bytes that need to be hashed.
     */
    protected long[] range;

    /**
     * The PdfDocument.
     */
    protected PdfDocument document;

    /**
     * The crypto dictionary.
     */
    protected PdfSignature cryptoDictionary;

    /**
     * Holds value of property signatureEvent.
     */
    protected ISignatureEvent signatureEvent;

    /**
     * OutputStream for the bytes of the document.
     */
    protected OutputStream originalOS;

    /**
     * Outputstream that temporarily holds the output in memory.
     */
    protected ByteArrayOutputStream temporaryOS;

    /**
     * Tempfile to hold the output temporarily.
     */
    protected File tempFile;

    /**
     * Name and content of keys that can only be added in the close() method.
     */
    protected Map exclusionLocations;

    /**
     * Indicates if the pdf document has already been pre-closed.
     */
    protected boolean preClosed = false;

    /**
     * Signature field lock dictionary.
     */
    protected PdfSigFieldLock fieldLock;

    /**
     * The signature appearance.
     */
    protected PdfSignatureAppearance appearance;

    /**
     * Holds value of property signDate.
     */
    protected Calendar signDate = DateTimeUtil.getCurrentTimeCalendar();

    /**
     * Boolean to check if this PdfSigner instance has been closed already or not.
     */
    protected boolean closed = false;

    /**
     * Creates a PdfSigner instance. Uses a {@link java.io.ByteArrayOutputStream} instead of a temporary file.
     *
     * @param reader       PdfReader that reads the PDF file
     * @param outputStream OutputStream to write the signed PDF file
     * @param properties   {@link StampingProperties} for the signing document. Note that encryption will be
     *                     preserved regardless of what is set in properties.
     * @throws IOException if some I/O problem occurs
     */
    public PdfSigner(PdfReader reader, OutputStream outputStream, StampingProperties properties) throws IOException {
        this(reader, outputStream, null, properties);
    }

    /**
     * Creates a PdfSigner instance. Uses a {@link java.io.ByteArrayOutputStream} instead of a temporary file.
     *
     * @param reader       PdfReader that reads the PDF file
     * @param outputStream OutputStream to write the signed PDF file
     * @param path         File to which the output is temporarily written
     * @param properties   {@link StampingProperties} for the signing document. Note that encryption will be
     *                     preserved regardless of what is set in properties.
     * @throws IOException if some I/O problem occurs
     */
    public PdfSigner(PdfReader reader, OutputStream outputStream, String path, StampingProperties properties)
            throws IOException {
        StampingProperties localProps = new StampingProperties(properties).preserveEncryption();
        if (path == null) {
            temporaryOS = new ByteArrayOutputStream();
            document = initDocument(reader, new PdfWriter(temporaryOS), localProps);
        } else {
            this.tempFile = FileUtil.createTempFile(path);
            document = initDocument(reader, new PdfWriter(FileUtil.getFileOutputStream(tempFile)), localProps);
        }

        originalOS = outputStream;
        fieldName = getNewSigFieldName();
        appearance = new PdfSignatureAppearance(document, new Rectangle(0, 0), 1);
        appearance.setSignDate(signDate);
    }
    
    PdfSigner(PdfDocument document, OutputStream outputStream, ByteArrayOutputStream temporaryOS, File tempFile) {
        if (tempFile == null) {
            this.temporaryOS = temporaryOS;
        } else {
            this.tempFile = tempFile;
        }
        this.document = document;
        this.originalOS = outputStream;
        this.fieldName = getNewSigFieldName();
        this.appearance = new PdfSignatureAppearance(document, new Rectangle(0, 0), 1);
        this.appearance.setSignDate(this.signDate);
    }

    protected PdfDocument initDocument(PdfReader reader, PdfWriter writer, StampingProperties properties) {
        return new PdfAAgnosticPdfDocument(reader, writer, properties);
    }

    /**
     * Gets the signature date.
     *
     * @return Calendar set to the signature date
     */
    public java.util.Calendar getSignDate() {
        return signDate;
    }

    /**
     * Sets the signature date.
     *
     * @param signDate the signature date
     */
    public void setSignDate(java.util.Calendar signDate) {
        this.signDate = signDate;
        this.appearance.setSignDate(signDate);
    }

    /**
     * Provides access to a signature appearance object. Use it to
     * customize the appearance of the signature.
     * 

* Be aware: *

    *
  • If you create new signature field (either use {@link #setFieldName} with * the name that doesn't exist in the document or don't specify it at all) then * the signature is invisible by default. *
  • If you sign already existing field, then the signature appearance object * is modified to have all the properties (page num., rect etc.) consistent with * the state of the field (if you customized the appearance object * before the {@link #setFieldName} call you'll have to do it again) *
*

* * @return {@link PdfSignatureAppearance} object. */ @Deprecated public PdfSignatureAppearance getSignatureAppearance() { return appearance; } /** * Sets the signature field layout element to customize the appearance of the signature. Signer's sign date will * be set. * * @param appearance the {@link SignatureFieldAppearance} layout element. */ public void setSignatureAppearance(SignatureFieldAppearance appearance) { this.appearance.setSignatureAppearance(appearance); } /** * Returns the document's certification level. * For possible values see {@link #setCertificationLevel(int)}. * * @return The certified status. */ public int getCertificationLevel() { return this.certificationLevel; } /** * Sets the document's certification level. * * @param certificationLevel a new certification level for a document. * Possible values are:

    *
  • {@link #NOT_CERTIFIED} *
  • {@link #CERTIFIED_NO_CHANGES_ALLOWED} *
  • {@link #CERTIFIED_FORM_FILLING} *
  • {@link #CERTIFIED_FORM_FILLING_AND_ANNOTATIONS} *
*/ public void setCertificationLevel(int certificationLevel) { this.certificationLevel = certificationLevel; } /** * Gets the field name. * * @return the field name */ public String getFieldName() { return fieldName; } /** * Returns the user made signature dictionary. This is the dictionary at the /V key * of the signature field. * * @return The user made signature dictionary. */ public PdfSignature getSignatureDictionary() { return cryptoDictionary; } /** * Getter for property signatureEvent. * * @return Value of property signatureEvent. */ public ISignatureEvent getSignatureEvent() { return this.signatureEvent; } /** * Sets the signature event to allow modification of the signature dictionary. * * @param signatureEvent the signature event */ public void setSignatureEvent(ISignatureEvent signatureEvent) { this.signatureEvent = signatureEvent; } /** * Gets a new signature field name that doesn't clash with any existing name. * * @return A new signature field name. */ public String getNewSigFieldName() { PdfAcroForm acroForm = PdfFormCreator.getAcroForm(document, true); String name = "Signature"; int step = 1; while (acroForm.getField(name + step) != null) { ++step; } return name + step; } /** * Sets the name indicating the field to be signed. The field can already be presented in the * document but shall not be signed. If the field is not presented in the document, it will be created. * * @param fieldName The name indicating the field to be signed. */ public void setFieldName(String fieldName) { if (fieldName != null) { PdfAcroForm acroForm = PdfFormCreator.getAcroForm(document, true); PdfFormField field = acroForm.getField(fieldName); if (field != null) { if (!PdfName.Sig.equals(field.getFormType())) { throw new IllegalArgumentException( SignExceptionMessageConstant.FIELD_TYPE_IS_NOT_A_SIGNATURE_FIELD_TYPE); } if (field.getValue() != null) { throw new IllegalArgumentException(SignExceptionMessageConstant.FIELD_ALREADY_SIGNED); } List widgets = field.getWidgets(); if (widgets.size() > 0) { PdfWidgetAnnotation widget = widgets.get(0); setPageRect(getWidgetRectangle(widget)); setPageNumber(getWidgetPageNumber(widget)); } } else { // Do not allow dots for new fields // For existing fields dots are allowed because there it might be fully qualified name if (fieldName.indexOf('.') >= 0) { throw new IllegalArgumentException(SignExceptionMessageConstant.FIELD_NAMES_CANNOT_CONTAIN_A_DOT); } } appearance.setFieldName(fieldName); this.fieldName = fieldName; } } /** * Gets the PdfDocument associated with this instance. * * @return the PdfDocument associated with this instance */ public PdfDocument getDocument() { return document; } /** * Sets the PdfDocument. * * @param document The PdfDocument */ protected void setDocument(PdfDocument document) { if (null == document.getReader()) { throw new IllegalArgumentException(SignExceptionMessageConstant.DOCUMENT_MUST_HAVE_READER); } this.document = document; } /** * Provides the page number of the signature field which this signature * appearance is associated with. * * @return The page number of the signature field which this signature * appearance is associated with. */ public int getPageNumber() { return appearance.getPageNumber(); } /** * Sets the page number of the signature field which this signature * appearance is associated with. Implicitly calls {@link PdfSigner#setPageRect} * which considers page number to process the rectangle correctly. * * @param pageNumber The page number of the signature field which * this signature appearance is associated with. * * @return this instance to support fluent interface. */ public PdfSigner setPageNumber(int pageNumber) { appearance.setPageNumber(pageNumber); return this; } /** * Provides the rectangle that represent the position and dimension * of the signature field in the page. * * @return the rectangle that represent the position and dimension * of the signature field in the page */ public Rectangle getPageRect() { return appearance.getPageRect(); } /** * Sets the rectangle that represent the position and dimension of * the signature field in the page. * * @param pageRect The rectangle that represents the position and * dimension of the signature field in the page. * * @return this instance to support fluent interface. */ public PdfSigner setPageRect(Rectangle pageRect) { appearance.setPageRect(pageRect); return this; } /** * Setter for the OutputStream. * * @param originalOS OutputStream for the bytes of the document */ public void setOriginalOutputStream(OutputStream originalOS) { this.originalOS = originalOS; } /** * Getter for the field lock dictionary. * * @return Field lock dictionary. */ public PdfSigFieldLock getFieldLockDict() { return fieldLock; } /** * Setter for the field lock dictionary. *

* Be aware: if a signature is created on an existing signature field, * then its /Lock dictionary takes the precedence (if it exists). * * @param fieldLock Field lock dictionary */ public void setFieldLockDict(PdfSigFieldLock fieldLock) { this.fieldLock = fieldLock; } /** * Returns the signature creator. * * @return The signature creator. */ public String getSignatureCreator() { return appearance.getSignatureCreator(); } /** * Sets the name of the application used to create the signature. * * @param signatureCreator A new name of the application signing a document. * * @return this instance to support fluent interface. */ public PdfSigner setSignatureCreator(String signatureCreator) { appearance.setSignatureCreator(signatureCreator); return this; } /** * Returns the signing contact. * * @return The signing contact. */ public String getContact() { return appearance.getContact(); } /** * Sets the signing contact. * * @param contact A new signing contact. * * @return this instance to support fluent interface. */ public PdfSigner setContact(String contact) { appearance.setContact(contact); return this; } /** * Signs the document using the detached mode, CMS or CAdES equivalent. *

* NOTE: This method closes the underlying pdf document. This means, that current instance * of PdfSigner cannot be used after this method call. * * @param externalSignature the interface providing the actual signing * @param chain the certificate chain * @param crlList the CRL list * @param ocspClient the OCSP client * @param tsaClient the Timestamp client * @param externalDigest an implementation that provides the digest * @param estimatedSize the reserved size for the signature. It will be estimated if 0 * @param sigtype Either Signature.CMS or Signature.CADES * @throws IOException if some I/O problem occurs * @throws GeneralSecurityException if some problem during apply security algorithms occurs */ public void signDetached(IExternalDigest externalDigest, IExternalSignature externalSignature, Certificate[] chain, Collection crlList, IOcspClient ocspClient, ITSAClient tsaClient, int estimatedSize, CryptoStandard sigtype) throws IOException, GeneralSecurityException { signDetached(externalDigest, externalSignature, chain, crlList, ocspClient, tsaClient, estimatedSize, sigtype, (ISignaturePolicyIdentifier) null); } /** * Signs the document using the detached mode, CMS or CAdES equivalent. *

* NOTE: This method closes the underlying pdf document. This means, that current instance * of PdfSigner cannot be used after this method call. * * @param externalSignature the interface providing the actual signing * @param chain the certificate chain * @param crlList the CRL list * @param ocspClient the OCSP client * @param tsaClient the Timestamp client * @param externalDigest an implementation that provides the digest * @param estimatedSize the reserved size for the signature. It will be estimated if 0 * @param sigtype Either Signature.CMS or Signature.CADES * @param signaturePolicy the signature policy (for EPES signatures) * @throws IOException if some I/O problem occurs * @throws GeneralSecurityException if some problem during apply security algorithms occurs */ public void signDetached(IExternalDigest externalDigest, IExternalSignature externalSignature, Certificate[] chain, Collection crlList, IOcspClient ocspClient, ITSAClient tsaClient, int estimatedSize, CryptoStandard sigtype, SignaturePolicyInfo signaturePolicy) throws IOException, GeneralSecurityException { signDetached(externalDigest, externalSignature, chain, crlList, ocspClient, tsaClient, estimatedSize, sigtype, signaturePolicy.toSignaturePolicyIdentifier()); } /** * Signs the document using the detached mode, CMS or CAdES equivalent. *

* NOTE: This method closes the underlying pdf document. This means, that current instance * of PdfSigner cannot be used after this method call. * * @param externalSignature the interface providing the actual signing * @param chain the certificate chain * @param crlList the CRL list * @param ocspClient the OCSP client * @param tsaClient the Timestamp client * @param externalDigest an implementation that provides the digest * @param estimatedSize the reserved size for the signature. It will be estimated if 0 * @param sigtype Either Signature.CMS or Signature.CADES * @param signaturePolicy the signature policy (for EPES signatures) * @throws IOException if some I/O problem occurs * @throws GeneralSecurityException if some problem during apply security algorithms occurs */ public void signDetached(IExternalDigest externalDigest, IExternalSignature externalSignature, Certificate[] chain, Collection crlList, IOcspClient ocspClient, ITSAClient tsaClient, int estimatedSize, CryptoStandard sigtype, ISignaturePolicyIdentifier signaturePolicy) throws IOException, GeneralSecurityException { if (closed) { throw new PdfException(SignExceptionMessageConstant.THIS_INSTANCE_OF_PDF_SIGNER_ALREADY_CLOSED); } if (certificationLevel > 0 && isDocumentPdf2()) { if (documentContainsCertificationOrApprovalSignatures()) { throw new PdfException( SignExceptionMessageConstant.CERTIFICATION_SIGNATURE_CREATION_FAILED_DOC_SHALL_NOT_CONTAIN_SIGS); } } document.checkIsoConformance(sigtype == CryptoStandard.CADES, IsoKey.SIGNATURE_TYPE); Collection crlBytes = null; int i = 0; while (crlBytes == null && i < chain.length) { crlBytes = processCrl(chain[i++], crlList); } if (estimatedSize == 0) { estimatedSize = 8192; if (crlBytes != null) { for (byte[] element : crlBytes) { estimatedSize += element.length + 10; } } if (ocspClient != null) { estimatedSize += 4192; } if (tsaClient != null) { estimatedSize += 4192; } } appearance.setCertificate(chain[0]); if (sigtype == CryptoStandard.CADES && !isDocumentPdf2()) { addDeveloperExtension(PdfDeveloperExtension.ESIC_1_7_EXTENSIONLEVEL2); } if (externalSignature.getSignatureAlgorithmName().startsWith("Ed")) { addDeveloperExtension(PdfDeveloperExtension.ISO_32002); // Note: at this level of abstraction, we have no easy way of determining whether we are signing using a // specific ECDSA curve, so we can't auto-declare the extension safely, since we don't know whether // the curve is on the ISO/TS 32002 allowed curves list. That responsibility is delegated to the user. } String hashAlgorithm = externalSignature.getDigestAlgorithmName(); if(hashAlgorithm.startsWith("SHA3-") || hashAlgorithm.equals(DigestAlgorithms.SHAKE256)) { addDeveloperExtension(PdfDeveloperExtension.ISO_32001); } PdfSignature dic = new PdfSignature(PdfName.Adobe_PPKLite, sigtype == CryptoStandard.CADES ? PdfName.ETSI_CAdES_DETACHED : PdfName.Adbe_pkcs7_detached); dic.setReason(appearance.getReason()); dic.setLocation(appearance.getLocation()); dic.setSignatureCreator(getSignatureCreator()); dic.setContact(getContact()); dic.setDate(new PdfDate(getSignDate())); // time-stamp will over-rule this cryptoDictionary = dic; Map exc = new HashMap<>(); exc.put(PdfName.Contents, estimatedSize * 2 + 2); preClose(exc); PdfPKCS7 sgn = new PdfPKCS7((PrivateKey) null, chain, hashAlgorithm, null, externalDigest, false); if (signaturePolicy != null) { sgn.setSignaturePolicy(signaturePolicy); } InputStream data = getRangeStream(); byte[] hash = DigestAlgorithms.digest(data, SignUtils.getMessageDigest(hashAlgorithm, externalDigest)); List ocspList = new ArrayList<>(); if (chain.length > 1 && ocspClient != null) { for (int j = 0; j < chain.length - 1; ++j) { byte[] ocsp = ocspClient.getEncoded((X509Certificate) chain[j], (X509Certificate) chain[j + 1], null); if (ocsp != null) { ocspList.add(ocsp); } } } byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, sigtype, ocspList, crlBytes); byte[] extSignature = externalSignature.sign(sh); sgn.setExternalSignatureValue( extSignature, null, externalSignature.getSignatureAlgorithmName(), externalSignature.getSignatureMechanismParameters() ); byte[] encodedSig = sgn.getEncodedPKCS7(hash, sigtype, tsaClient, ocspList, crlBytes); if (estimatedSize < encodedSig.length) { throw new IOException("Not enough space"); } byte[] paddedSig = new byte[estimatedSize]; System.arraycopy(encodedSig, 0, paddedSig, 0, encodedSig.length); PdfDictionary dic2 = new PdfDictionary(); dic2.put(PdfName.Contents, new PdfString(paddedSig).setHexWriting(true)); close(dic2); closed = true; } /** * Sign the document using an external container, usually a PKCS7. The signature is fully composed * externally, iText will just put the container inside the document. *

* NOTE: This method closes the underlying pdf document. This means, that current instance * of PdfSigner cannot be used after this method call. * * @param externalSignatureContainer the interface providing the actual signing * @param estimatedSize the reserved size for the signature * @throws GeneralSecurityException if some problem during apply security algorithms occurs * @throws IOException if some I/O problem occurs */ public void signExternalContainer(IExternalSignatureContainer externalSignatureContainer, int estimatedSize) throws GeneralSecurityException, IOException { if (closed) { throw new PdfException(SignExceptionMessageConstant.THIS_INSTANCE_OF_PDF_SIGNER_ALREADY_CLOSED); } PdfSignature dic = new PdfSignature(); dic.setReason(appearance.getReason()); dic.setLocation(appearance.getLocation()); dic.setSignatureCreator(getSignatureCreator()); dic.setContact(getContact()); dic.setDate(new PdfDate(getSignDate())); // time-stamp will over-rule this externalSignatureContainer.modifySigningDictionary(dic.getPdfObject()); cryptoDictionary = dic; Map exc = new HashMap<>(); exc.put(PdfName.Contents, estimatedSize * 2 + 2); preClose(exc); InputStream data = getRangeStream(); byte[] encodedSig = externalSignatureContainer.sign(data); if (estimatedSize < encodedSig.length) { throw new IOException(SignExceptionMessageConstant.NOT_ENOUGH_SPACE); } byte[] paddedSig = new byte[estimatedSize]; System.arraycopy(encodedSig, 0, paddedSig, 0, encodedSig.length); PdfDictionary dic2 = new PdfDictionary(); dic2.put(PdfName.Contents, new PdfString(paddedSig).setHexWriting(true)); close(dic2); closed = true; } /** * Signs a document with a PAdES-LTV Timestamp. The document is closed at the end. *

* NOTE: This method closes the underlying pdf document. This means, that current instance * of PdfSigner cannot be used after this method call. * * @param tsa the timestamp generator * @param signatureName the signature name or null to have a name generated * automatically * @throws IOException if some I/O problem occurs * @throws GeneralSecurityException if some problem during apply security algorithms occurs */ public void timestamp(ITSAClient tsa, String signatureName) throws IOException, GeneralSecurityException { if (closed) { throw new PdfException(SignExceptionMessageConstant.THIS_INSTANCE_OF_PDF_SIGNER_ALREADY_CLOSED); } if (tsa == null) { throw new PdfException(SignExceptionMessageConstant.PROVIDED_TSA_CLIENT_IS_NULL); } int contentEstimated = tsa.getTokenSizeEstimate(); if (!isDocumentPdf2()) { addDeveloperExtension(PdfDeveloperExtension.ESIC_1_7_EXTENSIONLEVEL5); } setFieldName(signatureName); PdfSignature dic = new PdfSignature(PdfName.Adobe_PPKLite, PdfName.ETSI_RFC3161); dic.put(PdfName.Type, PdfName.DocTimeStamp); cryptoDictionary = dic; Map exc = new HashMap<>(); exc.put(PdfName.Contents, contentEstimated * 2 + 2); preClose(exc); InputStream data = getRangeStream(); MessageDigest messageDigest = tsa.getMessageDigest(); byte[] buf = new byte[4096]; int n; while ((n = data.read(buf)) > 0) { messageDigest.update(buf, 0, n); } byte[] tsImprint = messageDigest.digest(); byte[] tsToken; try { tsToken = tsa.getTimeStampToken(tsImprint); } catch (Exception e) { throw new GeneralSecurityException(e.getMessage(), e); } if (contentEstimated + 2 < tsToken.length) throw new IOException("Not enough space"); byte[] paddedSig = new byte[contentEstimated]; System.arraycopy(tsToken, 0, paddedSig, 0, tsToken.length); PdfDictionary dic2 = new PdfDictionary(); dic2.put(PdfName.Contents, new PdfString(paddedSig).setHexWriting(true)); close(dic2); closed = true; } /** * Signs a PDF where space was already reserved. * * @param document the original PDF * @param fieldName the field to sign. It must be the last field * @param outs the output PDF * @param externalSignatureContainer the signature container doing the actual signing. Only the * method ExternalSignatureContainer.sign is used * @throws IOException if some I/O problem occurs * @throws GeneralSecurityException if some problem during apply security algorithms occurs */ public static void signDeferred(PdfDocument document, String fieldName, OutputStream outs, IExternalSignatureContainer externalSignatureContainer) throws IOException, GeneralSecurityException { SignatureUtil signatureUtil = new SignatureUtil(document); PdfSignature signature = signatureUtil.getSignature(fieldName); if (signature == null) { throw new PdfException(SignExceptionMessageConstant.THERE_IS_NO_FIELD_IN_THE_DOCUMENT_WITH_SUCH_NAME) .setMessageParams(fieldName); } if (!signatureUtil.signatureCoversWholeDocument(fieldName)) { throw new PdfException( SignExceptionMessageConstant.SIGNATURE_WITH_THIS_NAME_IS_NOT_THE_LAST_IT_DOES_NOT_COVER_WHOLE_DOCUMENT ).setMessageParams(fieldName); } PdfArray b = signature.getByteRange(); long[] gaps = b.toLongArray(); if (b.size() != 4 || gaps[0] != 0) { throw new IllegalArgumentException("Single exclusion space supported"); } IRandomAccessSource readerSource = document.getReader().getSafeFile().createSourceView(); InputStream rg = new RASInputStream(new RandomAccessSourceFactory().createRanged(readerSource, gaps)); byte[] signedContent = externalSignatureContainer.sign(rg); int spaceAvailable = (int) (gaps[2] - gaps[1]) - 2; if ((spaceAvailable & 1) != 0) { throw new IllegalArgumentException("Gap is not a multiple of 2"); } spaceAvailable /= 2; if (spaceAvailable < signedContent.length) { throw new PdfException(SignExceptionMessageConstant.AVAILABLE_SPACE_IS_NOT_ENOUGH_FOR_SIGNATURE); } StreamUtil.copyBytes(readerSource, 0, gaps[1] + 1, outs); ByteBuffer bb = new ByteBuffer(spaceAvailable * 2); for (byte bi : signedContent) { bb.appendHex(bi); } int remain = (spaceAvailable - signedContent.length) * 2; for (int k = 0; k < remain; ++k) { bb.append((byte) 48); } byte[] bbArr = bb.toByteArray(); outs.write(bbArr); StreamUtil.copyBytes(readerSource, gaps[2] - 1, gaps[3] + 1, outs); } /** * Processes a CRL list. * * @param cert a Certificate if one of the CrlList implementations needs to retrieve the CRL URL from it. * @param crlList a list of CrlClient implementations * @return a collection of CRL bytes that can be embedded in a PDF * @throws CertificateEncodingException if an encoding error occurs in {@link Certificate}. */ protected Collection processCrl(Certificate cert, Collection crlList) throws CertificateEncodingException { if (crlList == null) { return null; } List crlBytes = new ArrayList<>(); for (ICrlClient cc : crlList) { if (cc == null) { continue; } Collection b = cc.getEncoded((X509Certificate) cert, null); if (b == null) { continue; } crlBytes.addAll(b); } return crlBytes.size() == 0 ? null : crlBytes; } protected void addDeveloperExtension(PdfDeveloperExtension extension) { document.getCatalog().addDeveloperExtension(extension); } /** * Checks if the document is in the process of closing. * * @return true if the document is in the process of closing, false otherwise */ protected boolean isPreClosed() { return preClosed; } /** * This is the first method to be called when using external signatures. The general sequence is: * preClose(), getDocumentBytes() and close(). *

* exclusionSizes must contain at least * the PdfName.CONTENTS key with the size that it will take in the * document. Note that due to the hex string coding this size should be byte_size*2+2. * * @param exclusionSizes Map with names and sizes to be excluded in the signature * calculation. The key is a PdfName and the value an Integer. At least the /Contents must be present * @throws IOException on error */ protected void preClose(Map exclusionSizes) throws IOException { if (preClosed) { throw new PdfException(SignExceptionMessageConstant.DOCUMENT_ALREADY_PRE_CLOSED); } preClosed = true; PdfAcroForm acroForm = PdfFormCreator.getAcroForm(document, true); SignatureUtil sgnUtil = new SignatureUtil(document); String name = getFieldName(); boolean fieldExist = sgnUtil.doesSignatureFieldExist(name); acroForm.setSignatureFlags(PdfAcroForm.SIGNATURE_EXIST | PdfAcroForm.APPEND_ONLY); PdfSigFieldLock fieldLock = null; if (cryptoDictionary == null) { throw new PdfException(SignExceptionMessageConstant.NO_CRYPTO_DICTIONARY_DEFINED); } cryptoDictionary.getPdfObject().makeIndirect(document); if (fieldExist) { fieldLock = populateExistingSignatureFormField(acroForm); } else { fieldLock = createNewSignatureFormField(acroForm, name); } exclusionLocations = new HashMap<>(); PdfLiteral lit = new PdfLiteral(80); exclusionLocations.put(PdfName.ByteRange, lit); cryptoDictionary.put(PdfName.ByteRange, lit); for (Map.Entry entry : exclusionSizes.entrySet()) { PdfName key = entry.getKey(); lit = new PdfLiteral((int) entry.getValue()); exclusionLocations.put(key, lit); cryptoDictionary.put(key, lit); } if (certificationLevel > 0) { addDocMDP(cryptoDictionary); } if (fieldLock != null) { addFieldMDP(cryptoDictionary, fieldLock); } if (signatureEvent != null) { signatureEvent.getSignatureDictionary(cryptoDictionary); } if (certificationLevel > 0) { // add DocMDP entry to root PdfDictionary docmdp = new PdfDictionary(); docmdp.put(PdfName.DocMDP, cryptoDictionary.getPdfObject()); document.getCatalog().put(PdfName.Perms, docmdp); document.getCatalog().setModified(); } document.checkIsoConformance(cryptoDictionary.getPdfObject(), IsoKey.SIGNATURE); cryptoDictionary.getPdfObject().flush(false); document.close(); range = new long[exclusionLocations.size() * 2]; long byteRangePosition = exclusionLocations.get(PdfName.ByteRange).getPosition(); exclusionLocations.remove(PdfName.ByteRange); int idx = 1; for (PdfLiteral lit1 : exclusionLocations.values()) { long n = lit1.getPosition(); range[idx++] = n; range[idx++] = lit1.getBytesCount() + n; } Arrays.sort(range, 1, range.length - 1); for (int k = 3; k < range.length - 2; k += 2) range[k] -= range[k - 1]; if (tempFile == null) { bout = temporaryOS.toByteArray(); range[range.length - 1] = bout.length - range[range.length - 2]; ByteArrayOutputStream bos = new ByteArrayOutputStream(); PdfOutputStream os = new PdfOutputStream(bos); os.write('['); for (int k = 0; k < range.length; ++k) { os.writeLong(range[k]).write(' '); } os.write(']'); System.arraycopy(bos.toByteArray(), 0, bout, (int) byteRangePosition, (int) bos.size()); } else { try { raf = FileUtil.getRandomAccessFile(tempFile); long len = raf.length(); range[range.length - 1] = len - range[range.length - 2]; ByteArrayOutputStream bos = new ByteArrayOutputStream(); PdfOutputStream os = new PdfOutputStream(bos); os.write('['); for (int k = 0; k < range.length; ++k) { os.writeLong(range[k]).write(' '); } os.write(']'); raf.seek(byteRangePosition); raf.write(bos.toByteArray(), 0, (int) bos.size()); } catch (IOException e) { try { raf.close(); } catch (Exception ignored) { } try { tempFile.delete(); } catch (Exception ignored) { } throw e; } } } /** * Populates already existing signature form field in the acroForm object. * This method is called during the {@link PdfSigner#preClose(Map)} method if the signature field already exists. * * @param acroForm {@link PdfAcroForm} object in which the signature field will be populated * @return signature field lock dictionary * @throws IOException if font for the appearance dictionary cannot be created */ protected PdfSigFieldLock populateExistingSignatureFormField(PdfAcroForm acroForm) throws IOException { PdfSignatureFormField sigField = (PdfSignatureFormField) acroForm.getField(fieldName); sigField.put(PdfName.V, cryptoDictionary.getPdfObject()); PdfSigFieldLock sigFieldLock = sigField.getSigFieldLockDictionary(); if (sigFieldLock == null && this.fieldLock != null) { this.fieldLock.getPdfObject().makeIndirect(document); sigField.put(PdfName.Lock, this.fieldLock.getPdfObject()); sigFieldLock = this.fieldLock; } sigField.put(PdfName.P, document.getPage(getPageNumber()).getPdfObject()); sigField.put(PdfName.V, cryptoDictionary.getPdfObject()); PdfObject obj = sigField.getPdfObject().get(PdfName.F); int flags = 0; if (obj != null && obj.isNumber()) { flags = ((PdfNumber) obj).intValue(); } flags |= PdfAnnotation.LOCKED; sigField.put(PdfName.F, new PdfNumber(flags)); sigField.disableFieldRegeneration(); sigField.setReuseAppearance(appearance.isReuseAppearance()) .setSignatureAppearanceLayer(appearance.getSignatureAppearanceLayer()) .setBackgroundLayer(appearance.getBackgroundLayer()); sigField.getFirstFormAnnotation().setFormFieldElement(appearance.getSignatureAppearance()); sigField.enableFieldRegeneration(); sigField.setModified(); return sigFieldLock; } /** * Creates new signature form field and adds it to the acroForm object. * This method is called during the {@link PdfSigner#preClose(Map)} method if the signature field doesn't exist. * * @param acroForm {@link PdfAcroForm} object in which new signature field will be added * @param name the name of the field * @return signature field lock dictionary * @throws IOException if font for the appearance dictionary cannot be created */ protected PdfSigFieldLock createNewSignatureFormField(PdfAcroForm acroForm, String name) throws IOException { PdfWidgetAnnotation widget = new PdfWidgetAnnotation(getPageRect()); widget.setFlags(PdfAnnotation.PRINT | PdfAnnotation.LOCKED); PdfSignatureFormField sigField = new SignatureFormFieldBuilder(document, name).createSignature(); sigField.put(PdfName.V, cryptoDictionary.getPdfObject()); sigField.addKid(widget); PdfSigFieldLock sigFieldLock = sigField.getSigFieldLockDictionary(); if (this.fieldLock != null) { this.fieldLock.getPdfObject().makeIndirect(document); sigField.put(PdfName.Lock, this.fieldLock.getPdfObject()); sigFieldLock = this.fieldLock; } int pagen = getPageNumber(); widget.setPage(document.getPage(pagen)); sigField.disableFieldRegeneration(); sigField.setReuseAppearance(appearance.isReuseAppearance()) .setSignatureAppearanceLayer(appearance.getSignatureAppearanceLayer()) .setBackgroundLayer(appearance.getBackgroundLayer()); sigField.getFirstFormAnnotation().setFormFieldElement(appearance.getSignatureAppearance()); sigField.enableFieldRegeneration(); acroForm.addField(sigField, document.getPage(pagen)); if (acroForm.getPdfObject().isIndirect()) { acroForm.setModified(); } else { //Acroform dictionary is a Direct dictionary, //for proper flushing, catalog needs to be marked as modified document.getCatalog().setModified(); } return sigFieldLock; } /** * Gets the document bytes that are hashable when using external signatures. * The general sequence is: * {@link #preClose(Map)}, {@link #getRangeStream()} and {@link #close(PdfDictionary)}. * * @return The {@link InputStream} of bytes to be signed. * @throws IOException if some I/O problem occurs */ protected InputStream getRangeStream() throws IOException { RandomAccessSourceFactory fac = new RandomAccessSourceFactory(); IRandomAccessSource randomAccessSource = fac.createRanged(getUnderlyingSource(), range); return new RASInputStream(randomAccessSource); } /** * This is the last method to be called when using external signatures. The general sequence is: * preClose(), getDocumentBytes() and close(). *

* update is a PdfDictionary that must have exactly the * same keys as the ones provided in {@link #preClose(Map)}. * * @param update a PdfDictionary with the key/value that will fill the holes defined * in {@link #preClose(Map)} * @throws IOException on error */ protected void close(PdfDictionary update) throws IOException { try { if (!preClosed) throw new PdfException(SignExceptionMessageConstant.DOCUMENT_MUST_BE_PRE_CLOSED); ByteArrayOutputStream bous = new ByteArrayOutputStream(); PdfOutputStream os = new PdfOutputStream(bous); for (PdfName key : update.keySet()) { PdfObject obj = update.get(key); PdfLiteral lit = exclusionLocations.get(key); if (lit == null) throw new IllegalArgumentException("The key didn't reserve space in preclose"); bous.reset(); os.write(obj); if (bous.size() > lit.getBytesCount()) { throw new IllegalArgumentException(SignExceptionMessageConstant.TOO_BIG_KEY); } if (tempFile == null) { System.arraycopy(bous.toByteArray(), 0, bout, (int) lit.getPosition(), (int) bous.size()); } else { raf.seek(lit.getPosition()); raf.write(bous.toByteArray(), 0, (int) bous.size()); } } if (update.size() != exclusionLocations.size()) throw new IllegalArgumentException("The update dictionary has less keys than required"); if (tempFile == null) { originalOS.write(bout, 0, bout.length); } else { if (originalOS != null) { raf.seek(0); long length = raf.length(); byte[] buf = new byte[8192]; while (length > 0) { int r = raf.read(buf, 0, (int) Math.min((long) buf.length, length)); if (r < 0) throw new EOFException("unexpected eof"); originalOS.write(buf, 0, r); length -= r; } } } } finally { if (tempFile != null) { raf.close(); if (originalOS != null) { tempFile.delete(); } } if (originalOS != null) { try { originalOS.close(); } catch (Exception ignored) { } } } } /** * Returns the underlying source. * * @return the underlying source * @throws IOException if some I/O problem occurs */ protected IRandomAccessSource getUnderlyingSource() throws IOException { RandomAccessSourceFactory fac = new RandomAccessSourceFactory(); return raf == null ? fac.createSource(bout) : fac.createSource(raf); } /** * Adds keys to the signature dictionary that define the certification level and the permissions. * This method is only used for Certifying signatures. * * @param crypto the signature dictionary */ protected void addDocMDP(PdfSignature crypto) { PdfDictionary reference = new PdfDictionary(); PdfDictionary transformParams = new PdfDictionary(); transformParams.put(PdfName.P, new PdfNumber(certificationLevel)); transformParams.put(PdfName.V, new PdfName("1.2")); transformParams.put(PdfName.Type, PdfName.TransformParams); reference.put(PdfName.TransformMethod, PdfName.DocMDP); reference.put(PdfName.Type, PdfName.SigRef); reference.put(PdfName.TransformParams, transformParams); reference.put(PdfName.Data, document.getTrailer().get(PdfName.Root)); PdfArray types = new PdfArray(); types.add(reference); crypto.put(PdfName.Reference, types); } /** * Adds keys to the signature dictionary that define the field permissions. * This method is only used for signatures that lock fields. * * @param crypto the signature dictionary * @param fieldLock the {@link PdfSigFieldLock} instance specified the field lock to be set */ protected void addFieldMDP(PdfSignature crypto, PdfSigFieldLock fieldLock) { PdfDictionary reference = new PdfDictionary(); PdfDictionary transformParams = new PdfDictionary(); transformParams.putAll(fieldLock.getPdfObject()); transformParams.put(PdfName.Type, PdfName.TransformParams); transformParams.put(PdfName.V, new PdfName("1.2")); reference.put(PdfName.TransformMethod, PdfName.FieldMDP); reference.put(PdfName.Type, PdfName.SigRef); reference.put(PdfName.TransformParams, transformParams); reference.put(PdfName.Data, document.getTrailer().get(PdfName.Root)); PdfArray types = crypto.getPdfObject().getAsArray(PdfName.Reference); if (types == null) { types = new PdfArray(); crypto.put(PdfName.Reference, types); } types.add(reference); } protected boolean documentContainsCertificationOrApprovalSignatures() { boolean containsCertificationOrApprovalSignature = false; PdfDictionary urSignature = null; PdfDictionary catalogPerms = document.getCatalog().getPdfObject().getAsDictionary(PdfName.Perms); if (catalogPerms != null) { urSignature = catalogPerms.getAsDictionary(PdfName.UR3); } PdfAcroForm acroForm = PdfFormCreator.getAcroForm(document, false); if (acroForm != null) { for (Map.Entry entry : acroForm.getAllFormFields().entrySet()) { PdfDictionary fieldDict = entry.getValue().getPdfObject(); if (!PdfName.Sig.equals(fieldDict.get(PdfName.FT))) continue; PdfDictionary sigDict = fieldDict.getAsDictionary(PdfName.V); if (sigDict == null) continue; PdfSignature pdfSignature = new PdfSignature(sigDict); if (pdfSignature.getContents() == null || pdfSignature.getByteRange() == null) { continue; } if (!pdfSignature.getType().equals(PdfName.DocTimeStamp) && sigDict != urSignature) { containsCertificationOrApprovalSignature = true; break; } } } return containsCertificationOrApprovalSignature; } /** * Get the rectangle associated to the provided widget. * * @param widget PdfWidgetAnnotation to extract the rectangle from * @return Rectangle */ protected Rectangle getWidgetRectangle(PdfWidgetAnnotation widget) { return widget.getRectangle().toRectangle(); } /** * Get the page number associated to the provided widget. * * @param widget PdfWidgetAnnotation from which to extract the page number * @return page number */ protected int getWidgetPageNumber(PdfWidgetAnnotation widget) { int pageNumber = 0; PdfDictionary pageDict = widget.getPdfObject().getAsDictionary(PdfName.P); if (pageDict != null) { pageNumber = document.getPageNumber(pageDict); } else { for (int i = 1; i <= document.getNumberOfPages(); i++) { PdfPage page = document.getPage(i); if (!page.isFlushed()) { if (page.containsAnnotation(widget)) { pageNumber = i; break; } } } } return pageNumber; } private boolean isDocumentPdf2() { return document.getPdfVersion().compareTo(PdfVersion.PDF_2_0) >= 0; } /** * An interface to retrieve the signature dictionary for modification. */ public interface ISignatureEvent { /** * Allows modification of the signature dictionary. * * @param sig The signature dictionary */ void getSignatureDictionary(PdfSignature sig); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy