iText5.x设置按钮外观而不破坏密封

时间:2018-09-07 10:04:48

标签: pdf itext digital-signature pades

这里是上下文:

  1. 我们将两个空白页面添加到现有的pdf中,每个页面包含一个空白按钮字段
  2. 我们对文件使用PAdES B-B印章,并拥有所有修改权利
  3. 我们修改了一个按钮以在其中插入图像

当我们尝试修改按钮外观以设置图像时,无论尝试如何,印章有效性都会因“未经授权的修改”而中断。

这是一个代码示例:

PdfReader pdfReader = new PdfReader("test.pdf");
PdfStamper pdfStamper = new PdfStamper(pdfReader, output, pdfReader.getPdfVersion(), true);

AcroFields acroFields = pdfStamper.getAcroFields();
String imageFieldId = "imageField1";
acroFields.setField(imageFieldId, Base64.encodeBytes(consentImage));

pdfStamper.close();
pdfReader.close();

我们还尝试了文档中推荐的方法,但没有成功:

PushbuttonField pbField = acroFields.getNewPushbuttonFromField(imageFieldId);
pbField.setImage(Image.getInstance("image1.jpg"));
acroFields.replacePushbuttonField(imageFieldId, pbField.getField());

问题是:我不知道iText是否支持这种修改类型,或者这是我们修改按钮的方式吗?

更新

如果将证书替换为简单的签名,我们可以设置按钮外观而不会破坏它。

1 个答案:

答案 0 :(得分:0)

为什么认证签名被破坏

你说

  

我们对文件使用PAdES B-B印章,并拥有所有修改权利

并不表示允许对文档进行所有可以想到的修改,而是允许对所有可能的修改。根据PDF规范,选择为:

  
      
  1. 不得更改文件;对文档的任何更改都将使签名无效。
  2.   
  3. 允许的更改应填写表格,实例化页面模板并签名;其他更改将使签名无效。
  4.   
  5. 允许的更改应与第2项相同,以及注释的创建,删除和修改;其他更改将使签名无效。
  6.   

因此,对于您的文档,允许的更改包括表单填写和任意注释操作。

不幸的是,iText 5在设置AcroForm按钮的“值”时,不仅将按钮的外观设置为按钮,而且

PushbuttonField pb = getNewPushbuttonFromField(name);
pb.setImage(img);
replacePushbuttonField(name, pb.getField());

即它基本上用类似的按钮替换前一个按钮。这样是不允许的。

为什么一个简单的批准签名没有被破坏

PDF规范不将允许的更改限制为仅由批准签名签署的文档(除非在FieldMDP转换中明确给出了限制)。

Adob​​e曾经声称他们确实将允许对已签名但未经认证的文档(例如那些限制值为3加“添加签名字段”)的认证文档进行的更改限制为此类更改。 this answer,但显然它们在其他方面也有点松懈。特别是在当前的Adobe Reader版本中,仅针对当前情况警告“具有属性更改的表单域”。

其他并发症

所讨论的PDF实际上不仅具有AcroForm表单定义,而且具有类似的XFA表单定义,它是一个混合表单文档。因此,要更改两个表单定义中的图像,还必须考虑XFA表单的填写。

幸运的是,iText 5将图像填充到XFA表单中的方式并没有使Adobe Reader认为封印破裂。

如何设置按钮图像而不破坏印章

为了不破坏印章,我们必须设置按钮图像而不更改底层形式,仅更改小部件即可。因此,以下代码试图仅更改按钮的外观:

PdfReader pdfReader = new PdfReader(SOURCE);
PdfStamper pdfStamper = new PdfStamper(pdfReader, TARGET, pdfReader.getPdfVersion(), true);
byte[] bytes = IMAGE_BYTES;

AcroFields acroFields = pdfStamper.getAcroFields();
String name = "mainform[0].subform_0[0].image_0_0[0]";
String value = Base64.getEncoder().encodeToString(bytes);
Image image = Image.getInstance(bytes);

XfaForm xfa = acroFields.getXfa();
if (xfa.isXfaPresent()) {
    name = xfa.findFieldName(name, acroFields);
    if (name != null) {
        String shortName = XfaForm.Xml2Som.getShortName(name);
        Node xn = xfa.findDatasetsNode(shortName);
        if (xn == null) {
            xn = xfa.getDatasetsSom().insertNode(xfa.getDatasetsNode(), shortName);
        }
        xfa.setNodeText(xn, value);
    }
}

PdfDictionary widget = acroFields.getFieldItem(name).getWidget(0);
PdfArray boxArray = widget.getAsArray(PdfName.RECT);
Rectangle box = new Rectangle(boxArray.getAsNumber(0).floatValue(), boxArray.getAsNumber(1).floatValue(), boxArray.getAsNumber(2).floatValue(), boxArray.getAsNumber(3).floatValue());

float ratioImage = image.getWidth() / image.getHeight();
float ratioBox = box.getWidth() / box.getHeight();
boolean fillHorizontally = ratioImage > ratioBox;
float width = fillHorizontally ? 1 : ratioBox / ratioImage;
float height = fillHorizontally ? ratioImage / ratioBox : 1;
float xOffset = 0; // centered: (width - 1) / 2;
float yOffset = height - 1; // centered: (height - 1) / 2;
PdfAppearance app = PdfAppearance.createAppearance(pdfStamper.getWriter(), width, height);
app.addImage(image, 1, 0, 0, 1, xOffset, yOffset);
PdfDictionary dic = (PdfDictionary)widget.get(PdfName.AP);
if (dic == null)
    dic = new PdfDictionary();
dic.put(PdfAnnotation.APPEARANCE_NORMAL, app.getIndirectReference());
widget.put(PdfName.AP, dic);
pdfStamper.markUsed(widget);

pdfStamper.close();
pdfReader.close();

SetImageInSignedPdf测试testSetInXfaAndAppearanceSampleCert

在我的测试中,这导致图像在支持XFA表单的查看器和不支持XFA表单的查看器中均可见,并且Adobe Reader不认为封印已损坏。

但是,请注意,我仅使用您的示例文档进行了开发和测试;有可能不考虑某些边境条件。