代码之家  ›  专栏  ›  技术社区  ›  Renat Gatin

PDFBox是否可以签署所有“批准签名”,而不是始终将第一个签名作为“认证”?

  •  1
  • Renat Gatin  · 技术社区  · 7 年前

    作为Adobe文章 "Digital Signatures in a PDF" 声明:

    PDF定义了两种类型的签名:批准和认证。这个 区别如下: 批准: 文档中可以有任意数量的批准签名。该字段可以选择性地与FieldMDP关联 认证: 只能有一个认证签名,并且必须是文档中的第一个。字段始终关联 使用DocMDP。

    https://github.com/apache/pdfbox/blob/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/CreateVisibleSignature.java 为了应用多个签名,我只是用不同的签名占位符和图像多次运行同一代码。

    但我所区别的是,即使我运行相同的代码,它总是将第一个签名设置为认证,而所有其他签名都设置为批准。

    enter image description here

    但在我的情况下,我不希望一个文件被认证,我只需要所有签名都是Apploval类型,包括第一个。我知道我可以先证明签名,但我还是不想证明文件。

    下面是我的示例代码用法(其他类在上面的GitHub链接中):

    public class SignnerPDFBoxExample extends CreateSignatureBase {
    
        private SignatureOptions signatureOptions;
        private PDVisibleSignDesigner visibleSignDesigner;
        private final PDVisibleSigProperties visibleSignatureProperties = new PDVisibleSigProperties();
        private boolean lateExternalSigning = false;
    
        public static void main(String[] args) throws Exception {
    
            File ksFile = new File("keystore.jks");
            KeyStore keystore = KeyStore.getInstance("JKS");
            char[] pin = "123456".toCharArray();
            keystore.load(new FileInputStream(ksFile), pin);
    
            SignnerPDFBoxExample signer = new SignnerPDFBoxExample(keystore, pin.clone());
            String inputFilename = "Four_Signature_template.pdf";
    
            File documentFile = new File(inputFilename);
            File signedDocumentFile;
            int page = 1;
            try (FileInputStream imageStream = new FileInputStream("client_signature.jpg"))
            {
                String name = documentFile.getName();
                String substring = name.substring(0, name.lastIndexOf('.'));
                signedDocumentFile = new File(documentFile.getParent(), substring + "_signed.pdf");
                // page is 1-based here
                signer.setVisibleSignDesigner(inputFilename, 0, 0, -50, imageStream, page);
            }
            signer.setVisibleSignatureProperties("name", "location", "Signed using PDFBox", 0, page, true);
            signer.signPDF(documentFile, signedDocumentFile, null, "certifySignature");
        }
    
        public boolean isLateExternalSigning()
        {
            return lateExternalSigning;
        }
    
        /**
         * Set late external signing. Enable this if you want to activate the demo code where the
         * signature is kept and added in an extra step without using PDFBox methods. This is disabled
         * by default.
         *
         * @param lateExternalSigning
         */
        public void setLateExternalSigning(boolean lateExternalSigning)
        {
            this.lateExternalSigning = lateExternalSigning;
        }
    
        /**
         * Set visible signature designer for a new signature field.
         * 
         * @param filename
         * @param x position of the signature field
         * @param y position of the signature field
         * @param zoomPercent
         * @param imageStream
         * @param page the signature should be placed on
         * @throws IOException
         */
        public void setVisibleSignDesigner(String filename, int x, int y, int zoomPercent, 
                FileInputStream imageStream, int page) 
                throws IOException
        {
            visibleSignDesigner = new PDVisibleSignDesigner(filename, imageStream, page);
            visibleSignDesigner.xAxis(x).yAxis(y).zoom(zoomPercent).adjustForRotation();
        }
    
        /**
         * Set visible signature designer for an existing signature field.
         * 
         * @param zoomPercent
         * @param imageStream
         * @throws IOException
         */
        public void setVisibleSignDesigner(int zoomPercent, FileInputStream imageStream) 
                throws IOException
        {
            visibleSignDesigner = new PDVisibleSignDesigner(imageStream);
            visibleSignDesigner.zoom(zoomPercent);
        }
    
        /**
         * Set visible signature properties for new signature fields.
         * 
         * @param name
         * @param location
         * @param reason
         * @param preferredSize
         * @param page
         * @param visualSignEnabled
         * @throws IOException
         */
        public void setVisibleSignatureProperties(String name, String location, String reason, int preferredSize, 
                int page, boolean visualSignEnabled) throws IOException
        {
            visibleSignatureProperties.signerName(name).signerLocation(location).signatureReason(reason).
                    preferredSize(preferredSize).page(page).visualSignEnabled(visualSignEnabled).
                    setPdVisibleSignature(visibleSignDesigner);
        }
    
        /**
         * Set visible signature properties for existing signature fields.
         * 
         * @param name
         * @param location
         * @param reason
         * @param visualSignEnabled
         * @throws IOException
         */
        public void setVisibleSignatureProperties(String name, String location, String reason,
                boolean visualSignEnabled) throws IOException
        {
            visibleSignatureProperties.signerName(name).signerLocation(location).signatureReason(reason).
                    visualSignEnabled(visualSignEnabled).setPdVisibleSignature(visibleSignDesigner);
        }
    
        /**
         * Initialize the signature creator with a keystore (pkcs12) and pin that
         * should be used for the signature.
         *
         * @param keystore is a pkcs12 keystore.
         * @param pin is the pin for the keystore / private key
         * @throws KeyStoreException if the keystore has not been initialized (loaded)
         * @throws NoSuchAlgorithmException if the algorithm for recovering the key cannot be found
         * @throws UnrecoverableKeyException if the given password is wrong
         * @throws CertificateException if the certificate is not valid as signing time
         * @throws IOException if no certificate could be found
         */
        public SignnerPDFBoxExample(KeyStore keystore, char[] pin)
                throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, IOException, CertificateException
        {
            super(keystore, pin);
        }
    
        /**
         * Sign pdf file and create new file that ends with "_signed.pdf".
         *
         * @param inputFile The source pdf document file.
         * @param signedFile The file to be signed.
         * @param tsaClient optional TSA client
         * @throws IOException
         */
        public void signPDF(File inputFile, File signedFile, TSAClient tsaClient) throws IOException
        {
            this.signPDF(inputFile, signedFile, tsaClient, null);
        }
    
        /**
         * Sign pdf file and create new file that ends with "_signed.pdf".
         *
         * @param inputFile The source pdf document file.
         * @param signedFile The file to be signed.
         * @param tsaClient optional TSA client
         * @param signatureFieldName optional name of an existing (unsigned) signature field
         * @throws IOException
         */
        public void signPDF(File inputFile, File signedFile, TSAClient tsaClient, String signatureFieldName) throws IOException
        {
            setTsaClient(tsaClient);
    
            if (inputFile == null || !inputFile.exists())
            {
                throw new IOException("Document for signing does not exist");
            }
    
            // creating output document and prepare the IO streams.
            FileOutputStream fos = new FileOutputStream(signedFile);
    
            try (PDDocument doc = PDDocument.load(inputFile))
            {
                int accessPermissions = SigUtils.getMDPPermission(doc);
                if (accessPermissions == 1)
                {
                    throw new IllegalStateException("No changes to the document are permitted due to DocMDP transform parameters dictionary");
                }
                // Note that PDFBox has a bug that visual signing on certified files with permission 2
                // doesn't work properly, see PDFBOX-3699. As long as this issue is open, you may want to
                // be careful with such files.
    
                PDSignature signature;
    
                // sign a PDF with an existing empty signature, as created by the CreateEmptySignatureForm example.
                signature = findExistingSignature(doc, signatureFieldName);
    
                if (signature == null)
                {
                    // create signature dictionary
                    signature = new PDSignature();
                }
    
                // Optional: certify
                // can be done only if version is at least 1.5 and if not already set
                // doing this on a PDF/A-1b file fails validation by Adobe preflight (PDFBOX-3821)
                // PDF/A-1b requires PDF version 1.4 max, so don't increase the version on such files.
                if (doc.getVersion() >= 1.5f && accessPermissions == 0)
                {
                    SigUtils.setMDPPermission(doc, signature, 2);
                }
    
                PDAcroForm acroForm = doc.getDocumentCatalog().getAcroForm();
                if (acroForm != null && acroForm.getNeedAppearances())
                {
                    // PDFBOX-3738 NeedAppearances true results in visible signature becoming invisible 
                    // with Adobe Reader
                    if (acroForm.getFields().isEmpty())
                    {
                        // we can safely delete it if there are no fields
                        acroForm.getCOSObject().removeItem(COSName.NEED_APPEARANCES);
                        // note that if you've set MDP permissions, the removal of this item
                        // may result in Adobe Reader claiming that the document has been changed.
                        // and/or that field content won't be displayed properly.
                        // ==> decide what you prefer and adjust your code accordingly.
                    }
                    else
                    {
                        System.out.println("/NeedAppearances is set, signature may be ignored by Adobe Reader");
                    }
                }
    
                // default filter
                signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
    
                // subfilter for basic and PAdES Part 2 signatures
                signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
    
                if (visibleSignatureProperties != null)
                {
                    // this builds the signature structures in a separate document
                    visibleSignatureProperties.buildSignature();
    
                    signature.setName(visibleSignatureProperties.getSignerName());
                    signature.setLocation(visibleSignatureProperties.getSignerLocation());
                    signature.setReason(visibleSignatureProperties.getSignatureReason());
                }
    
                // the signing date, needed for valid signature
                signature.setSignDate(Calendar.getInstance());
    
                // do not set SignatureInterface instance, if external signing used
                SignatureInterface signatureInterface = isExternalSigning() ? null : this;
    
                // register signature dictionary and sign interface
                if (visibleSignatureProperties != null && visibleSignatureProperties.isVisualSignEnabled())
                {
                    signatureOptions = new SignatureOptions();
                    signatureOptions.setVisualSignature(visibleSignatureProperties.getVisibleSignature());
                    signatureOptions.setPage(visibleSignatureProperties.getPage() - 1);
                    doc.addSignature(signature, signatureInterface, signatureOptions);
                }
                else
                {
                    doc.addSignature(signature, signatureInterface);
                }
    
                if (isExternalSigning())
                {
                    System.out.println("Signing externally " + signedFile.getName());
                    ExternalSigningSupport externalSigning = doc.saveIncrementalForExternalSigning(fos);
                    // invoke external signature service
                    byte[] cmsSignature = sign(externalSigning.getContent());
    
                    // Explanation of late external signing (off by default):
                    // If you want to add the signature in a separate step, then set an empty byte array
                    // and call signature.getByteRange() and remember the offset signature.getByteRange()[1]+1.
                    // you can write the ascii hex signature at a later time even if you don't have this
                    // PDDocument object anymore, with classic java file random access methods.
                    // If you can't remember the offset value from ByteRange because your context has changed,
                    // then open the file with PDFBox, find the field with findExistingSignature() or
                    // PODDocument.getLastSignatureDictionary() and get the ByteRange from there.
                    // Close the file and then write the signature as explained earlier in this comment.
                    if (isLateExternalSigning())
                    {
                        // this saves the file with a 0 signature
                        externalSigning.setSignature(new byte[0]);
    
                        // remember the offset (add 1 because of "<")
                        int offset = signature.getByteRange()[1] + 1;
    
                        // now write the signature at the correct offset without any PDFBox methods
                        try (RandomAccessFile raf = new RandomAccessFile(signedFile, "rw"))
                        {
                            raf.seek(offset);
                            raf.write(Hex.getBytes(cmsSignature));
                        }
                    }
                    else
                    {
                        // set signature bytes received from the service and save the file
                        externalSigning.setSignature(cmsSignature);
                    }
                }
                else
                {
                    // write incremental (only for signing purpose)
                    doc.saveIncremental(fos);
                }
            }
    
            // Do not close signatureOptions before saving, because some COSStream objects within
            // are transferred to the signed document.
            // Do not allow signatureOptions get out of scope before saving, because then the COSDocument
            // in signature options might by closed by gc, which would close COSStream objects prematurely.
            // See https://issues.apache.org/jira/browse/PDFBOX-3743
            IOUtils.closeQuietly(signatureOptions);
        }
    
        // Find an existing signature (assumed to be empty). You will usually not need this.
        private PDSignature findExistingSignature(PDDocument doc, String sigFieldName)
        {
            PDSignature signature = null;
            PDSignatureField signatureField;
            PDAcroForm acroForm = doc.getDocumentCatalog().getAcroForm();
            if (acroForm != null)
            {
                signatureField = (PDSignatureField) acroForm.getField(sigFieldName);
                if (signatureField != null)
                {
                    // retrieve signature dictionary
                    signature = signatureField.getSignature();
                    if (signature == null)
                    {
                        signature = new PDSignature();
                        // after solving PDFBOX-3524
                        // signatureField.setValue(signature)
                        // until then:
                        signatureField.getCOSObject().setItem(COSName.V, signature);
                    }
                    else
                    {
                        throw new IllegalStateException("The signature field " + sigFieldName + " is already signed.");
                    }
                }
            }
            return signature;
        }
    
        /**
         * This will print the usage for this program.
         */
        private static void usage()
        {
            System.err.println("Usage: java " + CreateVisibleSignature.class.getName()
                    + " <pkcs12-keystore-file> <pin> <input-pdf> <sign-image>\n" + "" +
                               "options:\n" +
                               "  -tsa <url>    sign timestamp using the given TSA server\n"+
                               "  -e            sign using external signature creation scenario");
        }
    
    
    }
    
    1 回复  |  直到 7 年前
        1
  •  5
  •   mkl    7 年前

    你的 signPDF 方法包含以下代码:

            // Optional: certify
            // can be done only if version is at least 1.5 and if not already set
            // doing this on a PDF/A-1b file fails validation by Adobe preflight (PDFBOX-3821)
            // PDF/A-1b requires PDF version 1.4 max, so don't increase the version on such files.
            if (doc.getVersion() >= 1.5f && accessPermissions == 0)
            {
                SigUtils.setMDPPermission(doc, signature, 2);
            }
    

    如果您不想以认证签名开始,请删除此项 setMDPPermission 呼叫