附件损害赔偿签名第2部分 [英] Attachment damages signature part 2

查看:71
本文介绍了附件损害赔偿签名第2部分的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我创建了代码,将图像添加到现有的pdf文档中,然后使用PDFBox对其进行签名(请参见下面的代码).

I created code that adds an image to an existing pdf document and then signs it, all using PDFBox (see code below).

代码很好地添加了图像和签名.但是,在某些文档中,Acrobat Reader抱怨签名字节范围无效."

The code nicely adds the image and the signature. However, in some documents, Acrobat Reader complains that "The signature byte range is invalid."

问题似乎与问题中描述的问题相同.该问题的答案更详细地描述了该问题:问题是我的代码在文档(流和表)中留下了交叉引用类型的混合.确实,由于这会造成一些问题,某些文档甚至无法打开.

The problem seems to be the same as the problem described in this question. The answer to that question describes the problem in more detail: the problem is that my code leaves a mix of cross reference types in the document (streams and tables). Indeed, some documents won't even open because of the problems that this creates.

我的问题是:如何预防这种情况?如何在不创建多个交叉引用类型的情况下将图像添加到现有的pdf文档?

My question is: how do I prevent this? How do I add an image to an existing pdf document without creating multiple cross reference types?

public class TC3 implements SignatureInterface{

private char[] pin = "123456".toCharArray();
private BouncyCastleProvider provider = new BouncyCastleProvider();
private PrivateKey privKey;
private Certificate[] cert;

public TC3() throws Exception{
    Security.addProvider(provider);
    KeyStore keystore = KeyStore.getInstance("PKCS12", provider);        
    keystore.load(new FileInputStream(new File("resources/IIS_keystore.pfx")), pin.clone());
    String alias = keystore.aliases().nextElement();
    privKey = (PrivateKey) keystore.getKey(alias, pin);
    cert = keystore.getCertificateChain(alias);
}

public void doSign() throws Exception{
    byte inputBytes[] = IOUtils.toByteArray(new FileInputStream("resources/rooster.pdf"));
    PDDocument pdDocument = PDDocument.load(new ByteArrayInputStream(inputBytes));
    PDJpeg ximage = new PDJpeg(pdDocument, ImageIO.read(new File("resources/logo.jpg")));
    PDPage page = (PDPage)pdDocument.getDocumentCatalog().getAllPages().get(0);
    PDPageContentStream contentStream = new PDPageContentStream(pdDocument, page, true, true);
    contentStream.drawXObject(ximage, 50, 50, 356, 40);
    contentStream.close();
    ByteArrayOutputStream os = new ByteArrayOutputStream();
    pdDocument.save(os);
    os.flush();        
    pdDocument.close();

    inputBytes = os.toByteArray(); 
    pdDocument = PDDocument.load(new ByteArrayInputStream(inputBytes));

    PDSignature signature = new PDSignature();
    signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
    signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
    signature.setName("signer name");
    signature.setLocation("signer location");
    signature.setReason("reason for signature");
    signature.setSignDate(Calendar.getInstance());

    pdDocument.addSignature(signature, this);

    File outputDocument = new File("resources/signed.pdf");
    ByteArrayInputStream fis = new ByteArrayInputStream(inputBytes);
    FileOutputStream fos = new FileOutputStream(outputDocument);
    byte[] buffer = new byte[8 * 1024];
    int c;
    while ((c = fis.read(buffer)) != -1)
    {
        fos.write(buffer, 0, c);
    }
    fis.close();
    FileInputStream is = new FileInputStream(outputDocument);

    pdDocument.saveIncremental(is, fos);
    pdDocument.close();     
}

public byte[] sign(InputStream content) {
    CMSProcessableInputStream input = new CMSProcessableInputStream(content);
    CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
    List<Certificate> certList = Arrays.asList(cert);
    CertStore certStore = null;
    try{
        certStore = CertStore.getInstance("Collection", new CollectionCertStoreParameters(certList), provider);
        gen.addSigner(privKey, (X509Certificate) certList.get(0), CMSSignedGenerator.DIGEST_SHA256);
        gen.addCertificatesAndCRLs(certStore);
        CMSSignedData signedData = gen.generate(input, false, provider);
        return signedData.getEncoded();
    }catch (Exception e){}
    return null;
}

public static void main(String[] args) throws Exception {
    new TC3().doSign();
}

推荐答案

问题

正如此答案中已经解释的那样,这里的问题是

The issue

As had already been explained in this answer, the issue at work here is that

  • 当非增量存储带有所添加图像的文档时,PDFBox 1.8.9会使用交叉引用表来执行此操作,而不管原始文件是使用表还是流.如果原始文件使用流,则将交叉引用流字典条目复制到预告片字典中;

...
0000033667 00000 n
0000033731 00000 n
trailer
<<
/DecodeParms <<
/Columns 4
/Predictor 12
>>
/Filter /FlateDecode
/ID [<5BD95916CAE5E84E9D964396022CBDCD> <6420B4547602C943AF37DD6C77496BE8>]
/Info 6 0 R
/Length 61
/Root 1 0 R
/Size 35
/Type /XRef
/W [1 2 1]
/Index [20 22]
>>
startxref
35917
%%EOF

(这里的大多数预告片条目都是无用的,甚至具有误导性,请参见下文.)

(Most of these trailer entries here are useless or even misleading, see below.)

当增量保存签名时,COSWriter.doWriteXRefInc使用COSDocument.isXRefStream来确定现有文档(我们如上所述存储的文档)是否使用了交叉引用流.如上所述,事实并非如此.但是,不幸的是,PDFBox 1.8.9中的COSDocument.isXRefStream被实现为

when incrementally saving the signature, COSWriter.doWriteXRefInc uses COSDocument.isXRefStream to determine whether the existing document (the one we stored as above) uses a cross reference stream. As mentioned above, it does not. Unfortunately, though, COSDocument.isXRefStream in PDFBox 1.8.9 is implemented as

public boolean isXRefStream()
{
    if (trailer != null)
    {
        return COSName.XREF.equals(trailer.getItem(COSName.TYPE));
    }
    return false;
}

因此,上面显示的具有误导性的 trailer 条目 Type 使PDFBox认为它必须使用交叉引用流.

Thus, the misleading trailer entry Type shown above make PDFBox think it has to use a cross reference stream.

结果是一个文档,其初始修订版以交叉引用表和怪异的预告片条目结尾,而其第二修订版以交叉引用流结尾.这是无效的.

The result is a document whose initial revision ends with a cross reference table and weird trailer entries and whose second revision ends with a cross reference stream. This is not valid.

但是,幸运的是,了解问题的产生方式是一种解决方法:删除麻烦的 trailer 条目,例如像这样:

Fortunately, though, understanding how the issue arises presents a work-around: Removing the troublesome trailer entry, e.g. like this:

    inputBytes = os.toByteArray();
    pdDocument = PDDocument.load(new ByteArrayInputStream(inputBytes));
    pdDocument.getDocument().getTrailer().removeItem(COSName.TYPE); // <<<<<<<<<< Remove misleading entry <<<<<<<<<<

使用此变通办法,已签名文档中的两个修订版都使用交叉引用表,并且签名有效.

With this work-around both revisions in the signed document use cross reference tables and the signature is valid.

请注意,如果将来的PDFBox版本更改为也使用外部参照流保存使用交叉引用流从源加载的文档,则必须再次删除变通方法.

Beware, if upcoming PDFBox versions change to save documents loaded from sources with cross reference streams using xref streams, too, the work-around must again be removed.

尽管如此,我认为在即将发布的1.xx版本中不会发生这种情况,而2.0.0版将引入一个从根本上改变的API,因此原始代码将无法立即使用然后无论如何.

I would assume, though, that won't happen in the 1.x.x versions to come, and version 2.0.0 will introduce a fundamentally changed API, so the original code won't work out-of-the-box then anyhow.

我也尝试了其他方法来规避此问题,尝试

I tried other ways, too, to circumvent this problem, trying to

  • 也将第一个操作存储为增量更新,或者
  • 在与签名相同的增量更新期间添加图像,

cf. SignLikeUnOriginalToo.java ,但失败. PDFBox 1.8.9增量更新似乎仅适用于添加签名.

cf. SignLikeUnOriginalToo.java, but failed. PDFBox 1.8.9 incremental updates only seem to properly work for adding signatures.

在进一步研究了使用PDFBox创建其他修订版之后,我再次尝试了其他想法,现在成功了!

After looking into the creation of additional revisions using PDFBox some more, I tried the other ideas again and now succeeded!

关键部分是将添加和更改的对象标记为已更新,包括文档目录中的路径.

The crucial part is to mark the added and changed objects as updated, including a path from the document catalog.

应用第一个想法(将图像作为显式的中间修订版本添加)等同于doSign中的此更改:

Applying the first idea (adding the image as an explicit intermediate revision) amounts to this change in doSign:

...
FileOutputStream fos = new FileOutputStream(intermediateDocument);
FileInputStream fis = new FileInputStream(intermediateDocument);

byte inputBytes[] = IOUtils.toByteArray(inputStream);

PDDocument pdDocument = PDDocument.load(new ByteArrayInputStream(inputBytes));
PDJpeg ximage = new PDJpeg(pdDocument, ImageIO.read(logoStream));
PDPage page = (PDPage) pdDocument.getDocumentCatalog().getAllPages().get(0);
PDPageContentStream contentStream = new PDPageContentStream(pdDocument, page, true, true);
contentStream.drawXObject(ximage, 50, 50, 356, 40);
contentStream.close();

pdDocument.getDocumentCatalog().getCOSObject().setNeedToBeUpdate(true);
pdDocument.getDocumentCatalog().getPages().getCOSObject().setNeedToBeUpdate(true);
page.getCOSObject().setNeedToBeUpdate(true);
page.getResources().getCOSObject().setNeedToBeUpdate(true);
page.getResources().getCOSDictionary().getDictionaryObject(COSName.XOBJECT).setNeedToBeUpdate(true);
ximage.getCOSObject().setNeedToBeUpdate(true);

fos.write(inputBytes);
pdDocument.saveIncremental(fis, fos);
pdDocument.close();

pdDocument = PDDocument.load(intermediateDocument);

PDSignature signature = new PDSignature();
...

(如应用第二个想法(将图像添加为签名修订的一部分)等于doSign中的此更改:

Applying the second idea (adding the image as part of the signing revision) amounts to this change in doSign:

...
byte inputBytes[] = IOUtils.toByteArray(inputStream);
PDDocument pdDocument = PDDocument.load(new ByteArrayInputStream(inputBytes));

PDJpeg ximage = new PDJpeg(pdDocument, ImageIO.read(logoStream));
PDPage page = (PDPage) pdDocument.getDocumentCatalog().getAllPages().get(0);
PDPageContentStream contentStream = new PDPageContentStream(pdDocument, page, true, true);
contentStream.drawXObject(ximage, 50, 50, 356, 40);
contentStream.close();

page.getResources().getCOSObject().setNeedToBeUpdate(true);
page.getResources().getCOSDictionary().getDictionaryObject(COSName.XOBJECT).setNeedToBeUpdate(true);
ximage.getCOSObject().setNeedToBeUpdate(true);

PDSignature signature = new PDSignature();
...

(如这两种变体显然都比原始方法更好.

Both variants are clearly preferable to the original approach.

这篇关于附件损害赔偿签名第2部分的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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