存在批准签名时的证明文件

Certified Document when Approval Signature exist

与java一起使用Apache PDFBox进行签名和认证,如果有签名则认证无效,如果有批准则使用JsignPDF可以认证,过程是签名后做认证(盖章)文件

使用 JsignPDF 签名

JsignPDF 认证后的文档:

PDFBox认证无效

PDFBox 的功能经过一些编辑后尝试:

public void signPDF(File inputFile, File signedFile,  Rectangle2D humanRect, String tsaUrl, int page, String SignatureField, boolean isWithQR, String name, UserSignature userSignature) throws IOException, CertificateEncodingException, NoSuchAlgorithmException, OperatorCreationException, CMSException {
        if (inputFile == null || !inputFile.exists()) {
            throw new IOException("Document for signing does not exist");
        }

        setTsaUrl(tsaUrl);
        // creating output document and prepare the IO streams.

//        try (FileOutputStream fos = new FileOutputStream(signedFile);
//             PDDocument doc = Loader.loadPDF(inputFile)) {

        // creating output document and prepare the IO streams.
        FileOutputStream fos = new FileOutputStream(signedFile);

        // load document
        PDDocument doc = PDDocument.load(inputFile);

            int accessPermissions = SigUtils.getMDPPermission(doc);
            LogSystem.info("Document permission " + accessPermissions);
            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 = null;
            PDAcroForm acroForm = doc.getDocumentCatalog().getAcroForm(null);
            PDRectangle rect = null;

            // sign a PDF with an existing empty signature, as created by the CreateEmptySignatureForm example.
            if (acroForm != null) {
                signature = findExistingSignature(acroForm, SignatureField);
                if (signature != null) {
                    rect = acroForm.getField(SignatureField).getWidgets().get(0).getRectangle();
                }
            }

            if (signature == null) {
                // create signature dictionary
                signature = new PDSignature();
            }


            if (rect == null) {
                rect = createSignatureRectangle(doc, humanRect);
            }

            // 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);
//            }

            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);

            signature.setName("Name");
            signature.setLocation("Location");
            
            if(userSignature.getType().equals("sign"))
            {
                signature.setReason("Reason");
                if (accessPermissions == 0) {

                    COSDictionary sigDict = signature.getCOSObject();

                    // DocMDP specific stuff
                    COSDictionary transformParameters = new COSDictionary();
                    transformParameters.setItem(COSName.TYPE, COSName.TRANSFORM_PARAMS);
                    transformParameters.setInt(COSName.P, 0);
                    transformParameters.setNeedToBeUpdated(true);

                    COSDictionary referenceDict = new COSDictionary();
                    referenceDict.setItem(COSName.TYPE, COSName.SIG_REF);
                    referenceDict.setItem(COSName.TRANSFORM_METHOD, COSName.DOCMDP);
                    referenceDict.setItem(COSName.DIGEST_METHOD, COSName.getPDFName("SHA256"));
                    referenceDict.setItem(COSName.TRANSFORM_PARAMS, transformParameters);
                    referenceDict.setNeedToBeUpdated(true);

                    COSArray referenceArray = new COSArray();
                    referenceArray.add(referenceDict);
                    sigDict.setItem(COSName.REFERENCE, referenceArray);
                    referenceArray.setNeedToBeUpdated(true);

                    COSDictionary catalogDict = doc.getDocumentCatalog().getCOSObject();
                    COSDictionary permsDict = new COSDictionary();
                    catalogDict.setItem(COSName.PERMS, permsDict);
                    permsDict.setItem(COSName.DOCMDP, signature);
                    catalogDict.setNeedToBeUpdated(true);
                    permsDict.setNeedToBeUpdated(true);
                }
            }
            if(userSignature.getType().equals("seal"))
            {
                signature.setReason(userSignature.getQrText());
//                try {
//                    if (doc.getVersion() >= 1.5f && accessPermissions == 0)
//                    {
//                        SigUtils.setMDPPermission(doc, signature, 2);
//                    }
//                }catch(Exception e)
//                {
//                    e.printStackTrace();
//                }
                if (accessPermissions == 0) {
                    //            SigUtils.setMDPPermission(document, signature, 1);
                    COSDictionary sigDict = signature.getCOSObject();

                    // DocMDP specific stuff
                    COSDictionary transformParameters = new COSDictionary();
                    transformParameters.setItem(COSName.TYPE, COSName.TRANSFORM_PARAMS);
                    transformParameters.setInt(COSName.P, 2);
                    transformParameters.setNeedToBeUpdated(true);

                    COSDictionary referenceDict = new COSDictionary();
                    referenceDict.setItem(COSName.TYPE, COSName.SIG_REF);
                    referenceDict.setItem(COSName.TRANSFORM_METHOD, COSName.DOCMDP);
                    referenceDict.setItem(COSName.DIGEST_METHOD, COSName.getPDFName("SHA256"));
                    referenceDict.setItem(COSName.TRANSFORM_PARAMS, transformParameters);
                    referenceDict.setNeedToBeUpdated(true);

                    COSArray referenceArray = new COSArray();
                    referenceArray.add(referenceDict);
                    sigDict.setItem(COSName.REFERENCE, referenceArray);
                    referenceArray.setNeedToBeUpdated(true);

                    COSDictionary catalogDict = doc.getDocumentCatalog().getCOSObject();
                    COSDictionary permsDict = new COSDictionary();
                    catalogDict.setItem(COSName.PERMS, permsDict);
                    permsDict.setItem(COSName.DOCMDP, signature);
                    catalogDict.setNeedToBeUpdated(true);
                    permsDict.setNeedToBeUpdated(true);
                }
            }

            // 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
            signatureOptions = new SignatureOptions();

            signatureOptions.setVisualSignature(createVisualSignatureTemplate(doc, 0, rect, signature, isWithQR, name, userSignature.getDescOnly(), userSignature.getType(), userSignature.isVisible()));
            signatureOptions.setPage(page);



            doc.addSignature(signature, signatureInterface, signatureOptions);
            doc.getDocumentCatalog().getAcroForm().getField("Signature1").setPartialName(SignatureField);

            if (isExternalSigning()) {
                this.tsaUrl=tsaUrl;
//                ExternalSigningSupport externalSigning = doc.saveIncrementalForExternalSigning(fos);
//                // invoke external signature service
//                byte[] cmsSignature = sign(externalSigning.getContent());



                ExternalSigningSupport externalSigning = doc.saveIncrementalForExternalSigning(fos);

                // invoke external signature service
                byte[] cmsSignature =IOUtils.toByteArray(externalSigning.getContent());
                String sgn= signingProcess(cmsSignature);

                // set signature bytes received from the service
                externalSigning.setSignature(attachSignature(sgn));

            } else {
                // write incremental (only for signing purpose)
                doc.saveIncremental(fos);
            }
        doc.close();
//        } catch (CertificateEncodingException e) {
//            e.printStackTrace();
//        } catch (NoSuchAlgorithmException e) {
//            e.printStackTrace();
//        } catch (OperatorCreationException e) {
//            e.printStackTrace();
//        } catch (CMSException e) {
//            e.printStackTrace();
//        }

        // 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);
    }

设置 setMDPermission 以验证文档:

if (accessPermissions == 0) {

    COSDictionary sigDict = signature.getCOSObject();
    
    // DocMDP specific stuff
    COSDictionary transformParameters = new COSDictionary();
    transformParameters.setItem(COSName.TYPE, COSName.TRANSFORM_PARAMS);
    transformParameters.setInt(COSName.P, 1);
    transformParameters.setNeedToBeUpdated(true);
    
    COSDictionary referenceDict = new COSDictionary();
    referenceDict.setItem(COSName.TYPE, COSName.SIG_REF);
    referenceDict.setItem(COSName.TRANSFORM_METHOD, COSName.DOCMDP);
    referenceDict.setItem(COSName.DIGEST_METHOD, COSName.getPDFName("SHA256"));
    referenceDict.setItem(COSName.TRANSFORM_PARAMS, transformParameters);
    referenceDict.setNeedToBeUpdated(true);
    
    COSArray referenceArray = new COSArray();
    referenceArray.add(referenceDict);
    sigDict.setItem(COSName.REFERENCE, referenceArray);
    referenceArray.setNeedToBeUpdated(true);
    
    COSDictionary catalogDict = doc.getDocumentCatalog().getCOSObject();
    COSDictionary permsDict = new COSDictionary();
    catalogDict.setItem(COSName.PERMS, permsDict);
    permsDict.setItem(COSName.DOCMDP, signature);
    catalogDict.setNeedToBeUpdated(true);
    permsDict.setNeedToBeUpdated(true);
}

带有参数“sign”的签名和带有参数“seal”的认证

*更新,在 PDFBox 上第一个现有签名总是被认证,而不是第二个签名?最后一个标志可以认证并交换第一个吗?

有什么建议吗?

这个答案本质上是评论的更详细版本,本质上是发现你想做的事情是不可能的。

你问一个方法

Certify Document when Approval Signature exist

根据当前 PDF 规范:

ISO 32000-2:2020 subsection 12.8.1 "General" of 12.8 "Digital signatures"
A PDF document may contain the following standard types of signatures: [...] One or more approval signatures (also known as recipient signatures). These shall follow the certification signature if one is present.

因此,您无法将认证签名有效地添加到已经具有批准签名的 PDF。

(这并不意味着如果您在批准签名后添加认证签名,所有验证器都会自动检测到问题,更不用说正确显示原因了。许多验证器只执行严格来说必要的检查的子集。 ..)

More question if the certification signature come first then next is approval like 3 signature approval, can the last the certification setMDPPermission with 1 ? so at the end the document can't add more approval signature

setMDPPermission在签名上添加了DocMDP变换,这样的变换使签名成为认证签名。因此,在签署已具有批准签名的文档时使用此方法将创建无效的 PDF 或完全失败。

您可以使用批准签名锁定 PDF 文档,但是,如果您将 Lock 词典添加到带有 P[=35= 的签名字段] 条目 1. 但是请注意,这是一个 ISO 32000-2 功能,最初是作为 Adob​​e 对 ISO 32000-1 的扩展功能引入的。并非所有 PDF 查看器都支持 ISO 32000-2,因此某些查看器可能不尊重此条目。