PDFBox向已认证签名PDF添加注释致签名失效问题咨询
PDF数字签名与注释添加问题排查
摘要
向带有有效数字签名的特定文档添加注释时,即便采用增量保存且其他同类签名文档无此问题,现有签名仍会失效。唯一能复现该现象的操作是:签名时添加认证(Certified),限制编辑为「仅表单填写、评论和注释」(理论上允许此类操作),但后续以相同方式添加注释仍会导致签名失效。
问题
- 对PDF文档进行**认证(Certified)**操作,是否会实际禁止特定注释修改,即便认证明确允许注释?
- 我添加注释的方式是否存在特定错误,导致上述问题?
- 打开由jSignPDF创建的特定认证级别文档时,Acrobat Reader的签名栏会显示特定印章图标,Acrobat Reader依据哪个属性显示该图标?能否在应用认证级别时避免显示此图标?我有文档存在相同失效行为但无此图标。
场景
现有应用常规向未签名或内部签名的PDF添加印章注释,增量保存后Acrobat Reader会提示「签名后有更改但签名仍有效」。但处理外部签名的PDF时,添加注释会导致签名失效。
我尝试多种方式复现:修改PDF版本(1.4、1.6、1.7)、标记为Tagged、添加LTV信息(OCSP&CRL)、设置认证为「允许表单填写和注释」。使用工具包括LibreOffice 7.5.3.2、qpdf、jSignPDF 2.2.0,仅通过设置认证能复现问题,且确认是增量保存。
补充测试结论
- LibreOffice导出PDF经jSignPDF非认证签名后,添加注释签名仍有效;
- Microsoft Word导出PDF经相同签名和注释操作后,签名失效;
- LibreOffice导出PDF经jSignPDF设置「允许表单填写和注释」认证后,添加注释签名失效,是否为jSignPDF签名方式问题?
测试代码
... PDPage page = pddoc.getPage(0); List<PDAnnotation> annotations = page.getAnnotations(); // 优先使用图章注释,因为它不需要定义RC(富文本内容),且Acrobat Reader DC不会尝试修改文档来创建自己的RC表示 PDAnnotationRubberStamp t = new PDAnnotationRubberStamp(); t.setAnnotationName(name); // 可能起重要作用 t.setPrinted(true); // 始终可见 t.setReadOnly(true); // 不与用户交互 t.setContents(text); t.setHidden(false); t.setInvisible(false); t.setLocked(true); t.setPage(page); t.setLockedContents(true); PDFont font = null; try ( InputStream fs = PDFService.class.getResourceAsStream("/static/fonts/LiberationSans-Bold.ttf")) { ret = PDType0Font.load(pddoc, fs, true); // PDFBOX会发出警告,但我们忽略此警告 } catch (Exception ex) { System.out.println("无法加载字体 : " + ex.getMessage()); } font.getCOSObject().setNeedToBeUpdated(true); page.getResources().add(font); page.getResources().getCOSObject().setNeedToBeUpdated(true); page.getCOSObject().setNeedToBeUpdated(true); subsetFonts.add(font); // 应在添加完页面所有注释后进行字体子集化 PDRectangle rect = new PDRectangle(x, y, 100, 100); t.setRectangle(rect); Calendar c = Calendar.getInstance(); t.setCreationDate(c); t.setModifiedDate(c); PDAppearanceDictionary ap = new PDAppearanceDictionary(); PDAppearanceStream aps = new PDAppearanceStream(document); PDAppearanceContentStream apsContent = null; PDRectangle rect = ann.getRectangle(); rect = new PDRectangle(0, 0, rect.getWidth(), rect.getHeight()); // 必须使用相对坐标,否则注释内容不会显示 aps.setBBox(rect); // 将外观流的边界框设置为注释自身的尺寸 // 多行文本时,从最上方行开始绘制 float initialY = rect.getHeight() - _fontSize; // 嵌入Unicode字体(必须执行此操作,否则aps.getResources()为null,后续setFont会抛出NPE) PDResources res = new PDResources(); aps.setResources(res); try { // 直接在XObject的内容流上绘制 apsContent = new PDAppearanceContentStream(aps); apsContent.beginText(); apsContent.setFont(font, _fontSize); // 若无需支持希腊字符,使用PDType1Font.HELVETICA_BOLD即可 apsContent.newLineAtOffset(0, initialY); apsContent.showText(ann.getContents()); apsContent.endText(); } finally { if (apsContent != null) { try { apsContent.close(); } catch (Exception ex) { System.out.println(ex.getMessage()); } } } aps.getResources().getCOSObject().setNeedToBeUpdated(true); aps.getCOSObject().setNeedToBeUpdated(true); ap.setNormalAppearance(aps); ap.getCOSObject().setNeedToBeUpdated(true); t.setAppearance(ap); annotations.add(t); page.setAnnotations(annotations); t.getCOSObject().setNeedToBeUpdated(true); page.getResources().getCOSObject().setNeedToBeUpdated(true); page.getCOSObject().setNeedToBeUpdated(true); pddoc.getDocumentCatalog().getPages().getCOSObject().setNeedToBeUpdated(true); pddoc.getDocumentCatalog().getCOSObject().setNeedToBeUpdated(true); if (font.willBeSubset()) { font.subset(); font.getCOSObject().setNeedToBeUpdated(true); // 对应的页面资源也应标记为需更新——我们已在addAnnotation中处理 } pddoc.getDocumentCatalog().getPages().getCOSObject().setNeedToBeUpdated(true); pddoc.getDocumentCatalog().getCOSObject().setNeedToBeUpdated(true); ByteArrayOutputStream baos = new ByteArrayOutputStream(); pddoc.saveIncremental(baos); ...
内容的提问来源于stack exchange,提问作者hello_earth
相关产品推荐
相关产品推荐

