PDFBox 1.8.10:填充并签名文档,再次填充失败 [英] PDFBox 1.8.10: Fill and Sign Document, Filling again fails

查看:215
本文介绍了PDFBox 1.8.10:填充并签名文档,再次填充失败的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在上一个SO问题中 PDFBox 1.8 .10:填充并签名PDF"会产生无效签名 我解释了如何无法填充,然后使用PDFBox 1.8.10对PDF文档进行签名. 在某种帮助下解决了这个问题之后,我现在继续处理同一个主题. 从 doc_v2.pdf (文件的链接在下面!)开始,我对其进行填写并签名,结果是 doc_v2_fillsigned.pdf (一次性完成,并逐步保存). 再次打开编辑后的文档(再次使用PDFBox),然后尝试填充另一个字段.

然后保存文档将导致以下堆栈跟踪:

    Exception in thread "main" java.lang.NullPointerException
        at org.apache.pdfbox.pdmodel.interactive.form.PDAppearance.calculateFontSize(PDAppearance.java:930)
        at org.apache.pdfbox.pdmodel.interactive.form.PDAppearance.setAppearanceValue(PDAppearance.java:359)
        at org.apache.pdfbox.pdmodel.interactive.form.PDVariableText.setValue(PDVariableText.java:131)
        at com.c10n.scalibur.ehealthdemo.examples.PdfEditor.fill(PdfEditor.java:100)
        at com.c10n.scalibur.ehealthdemo.examples.SignPdf_ProfileLayer.start(SignPdf_ProfileLayer.java:66)
        at com.c10n.scalibur.ehealthdemo.examples.SignPdf_ProfileLayer.main(SignPdf_ProfileLayer.java:28)

在失败的填充运行中我该怎么做:

    File curentDocument new File("doc_v2_fillsigned.pdf);
    File newDocument = new File("doc_v2_fillsigned_filled.pdf);

    String fieldName ="New Emergency Contact";
    String value="test";
    PDDocument doc = null;

    try(FileOutputStream fos = new FileOutputStream(newDocument)){

        try(FileInputStream fis = new FileInputStream(currentDocument);){
            int c;
            while ((c = fis.read(buffer)) != -1) {
                fos.write(buffer, 0, c);
            }
        }

        doc = PDDocument.load(currentDocument);
        PDDocumentCatalog catalog = doc.getDocumentCatalog();

        catalog.getCOSObject().setNeedToBeUpdate(true);
        catalog.getPages().getCOSObject().setNeedToBeUpdate(true);

        PDAcroForm form = catalog.getAcroForm();

        form.getCOSObject().setNeedToBeUpdate(true);
        form.getDefaultResources().getCOSObject().setNeedToBeUpdate(true);

        PDField field = form.getField(fieldName);
        field.setValue(value); // here the exception occurs.

        // What should happen afterwards:
        field.getCOSObject().setNeedToBeUpdate(true);
        field.getAcroForm().getCOSObject().setNeedToBeUpdate(true);

        ((COSDictionary) field.getDictionary().getDictionaryObject("AP")).getDictionaryObject("N").setNeedToBeUpdate(true);

        try(FileInputStream fis = new FileInputStream(newDocument)){
            doc.saveIncremental(fis, fos);
        }
    }finally{
        if(null != doc){
            doc.close();
            doc=null;
        }
    }

文件:

空文档: https://www.dropbox.com /s/xf5pb0ng8k9zd4i/doc_v2.pdf?dl=0

已填充并签名的实例: https://www.dropbox. com/s/s8295tfyjpe1l4l/doc_v2_fillsigned.pdf?dl = 0

再次,欢迎您解决此问题!

更新:

mkl在注释中要求提供创建fillsigned pdf的代码.到目前为止,我了解到,仅签名就足以使上面的填充代码此后失败. 所以这是我的签名代码中的一些例外:

@Override
public byte[] sign(InputStream data) throws SignatureException, IOException {
    CMSTypedDataInputStream input = new CMSTypedDataInputStream(data);
    try {

        CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
        InputStream in = new ByteArrayInputStream(certData);

        X509Certificate signCert = (X509Certificate)certFactory.generateCertificate(in);

        ContentSigner signer = new MyContentSigner(profile);

        SignerInfoGenerator i = new JcaSignerInfoGeneratorBuilder(
                new JcaDigestCalculatorProviderBuilder().setProvider("BC").build())

        .build(signer,signCert);

        Store<?> certStore = new JcaCertStore(Collections.singletonList(signCert));

        CMSSignedDataGenerator cmsGen = new CMSSignedDataGenerator();

        cmsGen.addCertificates(certStore);
        cmsGen.addSignerInfoGenerator(i);

        CMSSignedData signedData = cmsGen.generate(input);

        byte[] result =signedData.getEncoded(); 
        return result;
    } catch (Exception e) {
        e.printStackTrace();
        throw new SignatureException(e);
    }
}

这会使用BouncyCastle 1.52创建签名,代码将从

调用

public void sign(SignatureInterface signer, String signatureFieldName, int pageNumber, String location, String reason, boolean lock) throws IOException, SignatureException{
    PDSignature signature = new PDSignature();
    signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE); // default filter
    signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED); // for visible sigs!

    signature.setLocation(location);
    signature.setReason(reason);

    signature.setSignDate(Calendar.getInstance());

    SignatureOptions options = makeSignatureVisible(signature,signatureFieldName, pageNumber, lock );

    doc.addSignature(signature, signer, options);
}

使用以下方法,在某些签名字段中产生可见的签名,在其中添加图片并将其正确放置:

SignatureOptions makeSignatureVisible( PDSignature signature, String fieldName, int pageNumber, boolean lock) throws IOException{
    PDDocumentCatalog catalog = doc.getDocumentCatalog();

    catalog.getCOSObject().setNeedToBeUpdate(true);
    catalog.getPages().getCOSObject().setNeedToBeUpdate(true);

    PDAcroForm form = catalog.getAcroForm();

    form.getCOSObject().setNeedToBeUpdate(true);
    form.getDefaultResources().getCOSObject().setNeedToBeUpdate(true);

    PDSignatureField field = (PDSignatureField) form.getField(fieldName);

    field.setSignature(signature);      
    field.setReadonly(lock);

    FileInputStream image = new FileInputStream("MUniverse_Signature.jpg");

    PDVisibleSignDesigner visibleSig =  new PDVisibleSignDesigner(newDocument.getName(), image, 1);

    PDRectangle area = getFieldArea(field);

    float max_width = area.getWidth();
    float max_height = area.getHeight();

    float scale = 1;

    if(max_height < visibleSig.getHeight()){
        scale = max_height / visibleSig.getHeight();
        System.out.println("scale: "+scale);
    }

    if(max_width < scale*visibleSig.getWidth()){
        scale = max_width / visibleSig.getWidth();
        System.out.println("scale: "+scale);
    }

    float zoom = ((scale-1)*100);

    visibleSig.zoom(zoom);

    PDPage page = (PDPage) doc.getDocumentCatalog().getAllPages().get(pageNumber);
    visibleSig.coordinates(area.getLowerLeftX(),page.getMediaBox().getHeight()-area.getUpperRightY());
    visibleSig.signatureFieldName(fieldName);

    PDVisibleSigProperties signatureProperties = new PDVisibleSigProperties();

    signatureProperties.signerName("name").signerLocation("location").signatureReason("Security")
    .visualSignEnabled(true).setPdVisibleSignature(visibleSig).buildSignature();

    SignatureOptions options = new SignatureOptions();
    options.setVisualSignature(signatureProperties);

    return options;
}

我怀疑这些片段不是必需的,并且在尝试使用增量保存来填充pdf时,使用PDFBox附带的签名示例也会导致相同的冲突.

致谢

丹尼尔

解决方案

问题的原因在于,在交互式表单字典的默认资源中,最初的字体填充和签名过程中不知何故丢失了.

strong>

PDFBox在填写表单时会尝试访问字体定义以创建外观流.它找不到它,因此最终失败了.

详细信息

在原始文档doc_v2.pdf中,交互式表单字典如下所示:

中的Acroform词典

您可以在默认资源 DR 字体词典中清楚地看到 ZaDb Helv 的条目字典.

相比之下,填写并签名的文档doc_v2_fillsigned.pdf的交互式表单字典如下:

如您所见,默认资源 DR 词典中的 Font 词典已丢失.

原因

OP进一步观察到:

我进一步打了一些球,签字就足够了.之后,填充失败.我认为,我的签名创建与PDFBox的示例中介绍的如何添加可见签名大致相同.

基于此,我简单地将PDFBox示例CreateVisibleSignature应用于OP的原始doc_v2.pdf文件.实际上,这已经删除了 Font 默认资源字典.

因此,这肯定看起来像是PDFBox错误.

PS:在Apache Jira上...

环顾四周 PDFBox吉拉实际上已经可以找到这方面的问题:

  • PDFBOX-2816-PDFBox在签名文档上进行了不允许的更改

    ...我在签名后注意到了这些结构上的变化:

    1. 从AcroForm字典中删除了默认资源(/DR);

  • PDFBOX-3017-改进文档签名

    改进签名代码:...

    • prepareNonVisualSignature清除AcroForm DR (acroForm.setDefaultResources(null)),如果还有其他表单域,则该效果不好

    这主要与非视觉签名有关,因此它是手头问题的垂饰.因此,一般来说 DR 似乎在使用PDFBox签名时会迷路

因此,这是一个已知问题.我不确定在确定优先级时是否会考虑PDFBox开发的投票,但是如果您有兴趣解决此问题,则投票不会有伤害...;)

In my previous SO question PDFBox 1.8.10: Fill and Sign PDF produces invalid signatures I explained, how I failed to fill and afterwards sign a PDF-Document, using PDFBox 1.8.10. After this got sorted out with some kind help, I now continue to work on the same topic. Starting with doc_v2.pdf (links to the file are below!), I fill and sign it, resulting in doc_v2_fillsigned.pdf (doing it in one go, saving it incrementally). Again I open the edited document (using again PDFBox) and try to fill another field.

Then saving the document leads to the following stack trace:

    Exception in thread "main" java.lang.NullPointerException
        at org.apache.pdfbox.pdmodel.interactive.form.PDAppearance.calculateFontSize(PDAppearance.java:930)
        at org.apache.pdfbox.pdmodel.interactive.form.PDAppearance.setAppearanceValue(PDAppearance.java:359)
        at org.apache.pdfbox.pdmodel.interactive.form.PDVariableText.setValue(PDVariableText.java:131)
        at com.c10n.scalibur.ehealthdemo.examples.PdfEditor.fill(PdfEditor.java:100)
        at com.c10n.scalibur.ehealthdemo.examples.SignPdf_ProfileLayer.start(SignPdf_ProfileLayer.java:66)
        at com.c10n.scalibur.ehealthdemo.examples.SignPdf_ProfileLayer.main(SignPdf_ProfileLayer.java:28)

What I do in the failing fill run:

    File curentDocument new File("doc_v2_fillsigned.pdf);
    File newDocument = new File("doc_v2_fillsigned_filled.pdf);

    String fieldName ="New Emergency Contact";
    String value="test";
    PDDocument doc = null;

    try(FileOutputStream fos = new FileOutputStream(newDocument)){

        try(FileInputStream fis = new FileInputStream(currentDocument);){
            int c;
            while ((c = fis.read(buffer)) != -1) {
                fos.write(buffer, 0, c);
            }
        }

        doc = PDDocument.load(currentDocument);
        PDDocumentCatalog catalog = doc.getDocumentCatalog();

        catalog.getCOSObject().setNeedToBeUpdate(true);
        catalog.getPages().getCOSObject().setNeedToBeUpdate(true);

        PDAcroForm form = catalog.getAcroForm();

        form.getCOSObject().setNeedToBeUpdate(true);
        form.getDefaultResources().getCOSObject().setNeedToBeUpdate(true);

        PDField field = form.getField(fieldName);
        field.setValue(value); // here the exception occurs.

        // What should happen afterwards:
        field.getCOSObject().setNeedToBeUpdate(true);
        field.getAcroForm().getCOSObject().setNeedToBeUpdate(true);

        ((COSDictionary) field.getDictionary().getDictionaryObject("AP")).getDictionaryObject("N").setNeedToBeUpdate(true);

        try(FileInputStream fis = new FileInputStream(newDocument)){
            doc.saveIncremental(fis, fos);
        }
    }finally{
        if(null != doc){
            doc.close();
            doc=null;
        }
    }

Files:

the empty document: https://www.dropbox.com/s/xf5pb0ng8k9zd4i/doc_v2.pdf?dl=0

filled and signed instance: https://www.dropbox.com/s/s8295tfyjpe1l4l/doc_v2_fillsigned.pdf?dl=0

Again, any help to resolve this is welcome!

Update:

mkl asked in the comments for the code to create the fillsigned pdf. So far I learned, that only signing suffices, to let the fill-code above fail afterwards. So here are some excepts from my signing code:

@Override
public byte[] sign(InputStream data) throws SignatureException, IOException {
    CMSTypedDataInputStream input = new CMSTypedDataInputStream(data);
    try {

        CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
        InputStream in = new ByteArrayInputStream(certData);

        X509Certificate signCert = (X509Certificate)certFactory.generateCertificate(in);

        ContentSigner signer = new MyContentSigner(profile);

        SignerInfoGenerator i = new JcaSignerInfoGeneratorBuilder(
                new JcaDigestCalculatorProviderBuilder().setProvider("BC").build())

        .build(signer,signCert);

        Store<?> certStore = new JcaCertStore(Collections.singletonList(signCert));

        CMSSignedDataGenerator cmsGen = new CMSSignedDataGenerator();

        cmsGen.addCertificates(certStore);
        cmsGen.addSignerInfoGenerator(i);

        CMSSignedData signedData = cmsGen.generate(input);

        byte[] result =signedData.getEncoded(); 
        return result;
    } catch (Exception e) {
        e.printStackTrace();
        throw new SignatureException(e);
    }
}

this creates the signature, using BouncyCastle 1.52, the code is called from

public void sign(SignatureInterface signer, String signatureFieldName, int pageNumber, String location, String reason, boolean lock) throws IOException, SignatureException{
    PDSignature signature = new PDSignature();
    signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE); // default filter
    signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED); // for visible sigs!

    signature.setLocation(location);
    signature.setReason(reason);

    signature.setSignDate(Calendar.getInstance());

    SignatureOptions options = makeSignatureVisible(signature,signatureFieldName, pageNumber, lock );

    doc.addSignature(signature, signer, options);
}

which uses the following method, to produce a visible signature in some signature field, adding a picture there and placing it correctly:

SignatureOptions makeSignatureVisible( PDSignature signature, String fieldName, int pageNumber, boolean lock) throws IOException{
    PDDocumentCatalog catalog = doc.getDocumentCatalog();

    catalog.getCOSObject().setNeedToBeUpdate(true);
    catalog.getPages().getCOSObject().setNeedToBeUpdate(true);

    PDAcroForm form = catalog.getAcroForm();

    form.getCOSObject().setNeedToBeUpdate(true);
    form.getDefaultResources().getCOSObject().setNeedToBeUpdate(true);

    PDSignatureField field = (PDSignatureField) form.getField(fieldName);

    field.setSignature(signature);      
    field.setReadonly(lock);

    FileInputStream image = new FileInputStream("MUniverse_Signature.jpg");

    PDVisibleSignDesigner visibleSig =  new PDVisibleSignDesigner(newDocument.getName(), image, 1);

    PDRectangle area = getFieldArea(field);

    float max_width = area.getWidth();
    float max_height = area.getHeight();

    float scale = 1;

    if(max_height < visibleSig.getHeight()){
        scale = max_height / visibleSig.getHeight();
        System.out.println("scale: "+scale);
    }

    if(max_width < scale*visibleSig.getWidth()){
        scale = max_width / visibleSig.getWidth();
        System.out.println("scale: "+scale);
    }

    float zoom = ((scale-1)*100);

    visibleSig.zoom(zoom);

    PDPage page = (PDPage) doc.getDocumentCatalog().getAllPages().get(pageNumber);
    visibleSig.coordinates(area.getLowerLeftX(),page.getMediaBox().getHeight()-area.getUpperRightY());
    visibleSig.signatureFieldName(fieldName);

    PDVisibleSigProperties signatureProperties = new PDVisibleSigProperties();

    signatureProperties.signerName("name").signerLocation("location").signatureReason("Security")
    .visualSignEnabled(true).setPdVisibleSignature(visibleSig).buildSignature();

    SignatureOptions options = new SignatureOptions();
    options.setVisualSignature(signatureProperties);

    return options;
}

I suspect these fragment are no neccessary, and applying the signing examples, which come with PDFBox results in the same conflict, when trying to fill the pdf with incrementle saving afterwards.

regards,

daniel

解决方案

The cause of the problem is that somehow during the original filling and signing the fonts in the default resources of the interactive form dictionary got lost.

PDFBox while filling in the form tries to access the font definition to create an appearance stream. It doesn't find it and, therefore, eventually fails.

In detail

In the original document doc_v2.pdf the interactive form dictionary looks like this:

You can clearly see the entries for ZaDb and Helv in the Font dictionary in the default resources DR dictionary.

In contrast the interactive form dictionary of the filled and signed document doc_v2_fillsigned.pdf looks like this:

As you see the the Font dictionary in the default resources DR dictionary is missing.

The cause

The OP observed further:

I played around some further and it suffices to sign. Afterwards, filling it fails. I assume, my signature creation is more or less the same as in PDFBox' example describing, how to ad a visible signature.

Based on this I simply applied the PDFBox example CreateVisibleSignature to the OP's original doc_v2.pdf file. Indeed, this already removed the Font default resource dictionary.

Thus, this definitively looks like a PDFBox bug.

PS: On the Apache Jira...

Looking around on the PDFBox Jira one actually already can find issues in this regard:

Thus, this is a known issue. I'm not sure whether PDFBox development takes issue votes into account when prioritizing, but if you are interested in this issue being resolved, a vote won't hurt... ;)

这篇关于PDFBox 1.8.10:填充并签名文档,再次填充失败的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆