我正在尝试向现有的数字签名pdf(认证签名)中添加一个空签名字段。
我有一个工作流,其中许多用户将签署该文档(批准签名),该文档创建时带有“n”个空签名字段,每个用户一个,我们的应用程序首先应用一个不可见的认证签名,然后每个用户可以在各自的字段中签署该文档,但由于工作流中意外的更改,其他用户可能希望签名,因此,我们希望添加相应的空签名字段,然后应用签名。
我试图将空字段(带有单元格事件的表)添加到认证文档中,但当我想添加它并关联字段时,它会破坏签名,我无法使其正常工作。
以下是用于签名、添加签名字段和设置签名字段选项的方法。我不知道我做错了什么。
public static String sign(SignRequest signRequest, File certificate, File unsignedDocument, File image, File icon)
throws FileNotFoundException, IOException, DocumentException, StreamParsingException, OCSPException,
OperatorException, URISyntaxException, WriterException, GeneralSecurityException, FontFormatException {
SignatureType sigType = Optional
.ofNullable(SignatureType.get(signRequest.getSignatureProperties().getSignatureType()))
.orElse(SignatureType.APPROVAL_SIGNATURE);
File signedDocument = File.createTempFile("signed",".pdf");
char[] pass = signRequest.getKeyStore().getPassword().toCharArray();
// Load certificate chain
BouncyCastleProvider provider = new BouncyCastleProvider();
Security.addProvider(provider);
KeyStore ks = KeyStore.getInstance("PKCS12", provider.getName());
ks.load(new FileInputStream(certificate.getAbsolutePath()), pass);
String alias = getAliasFromKeyStore(ks);
PrivateKey pk = (PrivateKey) ks.getKey(alias, pass);
Certificate[] chain = ks.getCertificateChain(alias);
// Creating the reader and the stamper
PdfReader reader = new PdfReader(FileUtils.openInputStream(unsignedDocument));
FileOutputStream os = new FileOutputStream(signedDocument);
PdfStamper stamper = PdfStamper.createSignature(reader, os, '\0', null, true);
PdfSignatureAppearance appearance = null;
// Certify o approval signature (approval is the default signature type)
switch (sigType) {
case CERTIFY_SIGNATURE:
if (reader.getAcroFields().getSignatureNames().size() <= 0) {
appearance = setSignatureFieldOptions(stamper.getSignatureAppearance(), reader, chain,
signRequest, image, icon, Boolean.TRUE);
} else {
appearance = setSignatureFieldOptions(stamper.getSignatureAppearance(), reader, chain,
signRequest, image, icon, Boolean.FALSE);
}
break;
case APPROVAL_SIGNATURE:
default:
appearance = setSignatureFieldOptions(stamper.getSignatureAppearance(), reader, chain, signRequest,
image, icon, Boolean.FALSE);
break;
}
// Adding LTV (optional)
OcspClient ocspClient = null;
List<CrlClient> crlList = null;
if (signRequest.getSignatureProperties().getLtv() == Boolean.TRUE) {
ocspClient = new OcspClientBouncyCastle(new OCSPVerifier(null, null));
CrlClient crlClient = new CrlClientOnline(chain);
crlList = new ArrayList<CrlClient>();
crlList.add(crlClient);
}
// Adding timestamp (optional)
TSAClient tsaClient = null;
if (signRequest.getTimestamp() != null
&& StringUtils.isNotBlank(signRequest.getTimestamp().getUrl())) {
tsaClient = new TSAClientBouncyCastle(signRequest.getTimestamp().getUrl(),
signRequest.getTimestamp().getUser(), signRequest.getTimestamp().getPassword());
}
// Creating the signature
ExternalSignature pks = new PrivateKeySignature(pk, signtRequest.getSignatureProperties().getAlgorithm(),
provider.getName());
ExternalDigest digest = new BouncyCastleDigest();
MakeSignature
.signDetached(appearance, digest, pks, chain, crlList, ocspClient, tsaClient,
calculateEstimatedSize(chain, ocspClient, tsaClient, crlList, getEstimatedSizeBonus()), CryptoStandard.CMS);
return signedDocument.getAbsolutePath();
}
private static PdfSignatureAppearance setSignatureFieldOptions(PdfSignatureAppearance appearance, PdfReader reader, Certificate[] chain, SignRequest signRequest, File image, File icon, Boolean certifySignature) throws MalformedURLException, IOException, DocumentException {
SignatureProperties sigProperties = signRequest.getSignatureProperties();
SignatureField sigField = sigProperties.getSignatureField();
// Creating the appearance
appearance.setSignatureCreator(Constant.SIGNATURE_CREATOR);
Optional.ofNullable(sigProperties.getReason()).ifPresent(appearance::setReason);
Optional.ofNullable(sigProperties.getLocation()).ifPresent(appearance::setLocation);
if (certifySignature) {
appearance.setCertificationLevel(PdfSignatureAppearance.CERTIFIED_FORM_FILLING_AND_ANNOTATIONS);
} else {
appearance.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED);
}
/**
* Signature Field Name
*/
BoundingBox box = sigProperties.getSignatureField().getBoundingBox();
String fieldName = sigField.getName();
int pageNumber = sigProperties.getSignatureField().getPage();
if (!sigField.isVisible()) {
if (StringUtils.isBlank(sigField.getName())) {
fieldName = generateFieldName();
appearance.setVisibleSignature(new Rectangle(0, 0, 0, 0), pageNumber, fieldName);
} else {
appearance.setVisibleSignature(new Rectangle(0, 0, 0, 0), pageNumber, fieldName);
}
} else {
Font font = FontFactory.getFont(Optional.ofNullable(sigField.getFontName()).orElse(BaseFont.HELVETICA),
Optional.ofNullable(sigField.getFontSize()).orElse(6));
Rectangle rect = null;
FieldPosition fieldPosition = null;
//ADD EMPTY FIELD
if (StringUtils.isBlank(sigField.getName()) && box != null) {
fieldName = generateFieldName();
rect = new Rectangle(box.getLowerLeftX(), box.getLowerLeftY(), box.getLowerLeftX() + box.getWidth(),
box.getLowerLeftY() + box.getHeight());
appearance.setVisibleSignature(rect, pageNumber, fieldName);
////////////////////////////////// TRY TO ADD EXTRA SIGNATURE FIELD///////////////////////////////////////////
Rectangle documentRectangle = reader.getPageSize(pageNumber);
PdfStamper stamper = appearance.getStamper();
float pageMargin = 10;
float tableMargin = 15;
int numberOfFields = 1; // 1 sigField
float headerWidth = (documentRectangle.getWidth() - (pageMargin * 2));
// Table with signature field
PdfPTable table = new PdfPTable(1);
table.setTotalWidth(headerWidth - (tableMargin * 4));
table.setLockedWidth(Boolean.TRUE);
table.setWidthPercentage(100);
float posXTable = (pageMargin + (headerWidth - table.getTotalWidth()) / 2);
float posYTable = 400; // custom y position
int height = 70; // custom height
for (int i = 0; i < numberOfFields; i++) {
String sigFieldName = String.format(Constant.SIGNATURE_FIELD_PREFIX + "%s", (i + 1));
table.addCell(createSignatureFieldCell(stamper, sigFieldName, height, pageNumber));
}
table.writeSelectedRows(0, -1, posXTable, posYTable, stamper.getOverContent(pageNumber));
////////////////////////////////// END TRY TO ADD EXTRA SIGNATURE FIELD///////////////////////////////////////////
} else {
//APPLY SIGNATURE TO EXISTING EMPTY FIELD
List<FieldPosition> acroFields = reader.getAcroFields().getFieldPositions(sigField.getName());
fieldPosition = acroFields.get(0);
appearance.setVisibleSignature(fieldName);
}
// --------------------------- Custom signature appearance ---------------------
PdfTemplate t = appearance.getLayer(2);
Rectangle sigRect = null;
if (fieldPosition != null) {
sigRect = fieldPosition.position;
} else {
sigRect = new Rectangle(box.getLowerLeftX(), box.getLowerLeftY(), box.getLowerLeftX() + box.getWidth(),
box.getLowerLeftY() + box.getHeight());
}
// Left rectangle
Rectangle leftRect = new Rectangle(0, 0, (sigRect.getWidth() / 5), (sigRect.getHeight() / 2));
ColumnText ct1 = new ColumnText(t);
ct1.setSimpleColumn(leftRect);
Image im1 = Image.getInstance(icon.getAbsolutePath());
float ratio1 = leftRect.getHeight() / im1.getHeight();
im1.scaleToFit(im1.getWidth() * ratio1, im1.getHeight() * ratio1);
Paragraph p = createParagraph("Digital sign", font, Constant.PARAGRAPH_LEADING, Constant.MARGIN * 9);
ct1.addElement(new Chunk(im1, Constant.MARGIN * 10, 0));
ct1.addElement(p);
ct1.go();
// Middle rectangle
Rectangle middleRect = new Rectangle((sigRect.getWidth() / 5), 0,
(leftRect.getWidth() + sigRect.getWidth() / 5), (sigRect.getHeight() / 2));
ColumnText ct2 = new ColumnText(t);
ct2.setSimpleColumn(middleRect);
if (visibleSignatureImage != null) {
Image im2 = Image.getInstance(image.getAbsolutePath());
float ratio2 = sigRect.getHeight() / im2.getHeight();
im2.scaleToFit(im2.getWidth() * ratio2, im2.getHeight() * ratio2);
ct2.addElement(new Chunk(im2, 0, 0));
ct2.go();
}
// TextFields
List<TextField> textFields = fillSignatureFieldText(chain, sigProperties, font);
// Right rectangle - Names
Rectangle rightRectNames = new Rectangle(
(Constant.MARGIN * 5 + leftRect.getWidth() + middleRect.getWidth()), 0,
(leftRect.getWidth() + middleRect.getWidth() + sigRect.getWidth() / 4),
sigRect.getHeight() - Constant.MARGIN);
ColumnText ct31 = new ColumnText(t);
ct31.setSimpleColumn(rightRectNames);
List<Paragraph> paragraphsNames = textFields.stream()
.map(e -> createParagraph(e.getName(), font, Constant.PARAGRAPH_LEADING, 0))
.collect(Collectors.toList());
paragraphsNames.forEach(ct31::addElement);
ct31.go();
// Right rectangle - Values
Rectangle rightRectValues = new Rectangle(
(Constant.MARGIN * 4 + leftRect.getWidth() + middleRect.getWidth() + rightRectNames.getWidth()), 0,
sigRect.getWidth(), (sigRect.getHeight() - Constant.MARGIN));
ColumnText ct32 = new ColumnText(t);
ct32.setSimpleColumn(rightRectValues);
List<Paragraph> paragraphsValues = textFields.stream()
.map(e -> createParagraph(e.getValue(), font, Constant.PARAGRAPH_LEADING, 0))
.collect(Collectors.toList());
paragraphsValues.forEach(ct32::addElement);
ct32.go();
// --------------------------- Custom signature appearance ---------------------
}
return appearance;
}
//this is used to first create the empty fields
protected static PdfPCell createSignatureFieldCell(PdfWriter writer, String name, int height) {
PdfPCell cell = new PdfPCell();
cell.setMinimumHeight(height);
cell.setBackgroundColor(BaseColor.WHITE);
PdfFormField field = PdfFormField.createSignature(writer);
field.setFieldName(name);
field.setFlags(PdfAnnotation.FLAGS_PRINT);
cell.setCellEvent(new MySignatureFieldEvent(field, null, 0));
return cell;
}
//this is used to try to add the extra empty field to signed document
protected static PdfPCell createSignatureFieldCell(PdfStamper stamper, String name, int height, int pageNumber) {
PdfPCell cell = new PdfPCell();
cell.setMinimumHeight(height);
cell.setBackgroundColor(BaseColor.WHITE);
PdfFormField field = PdfFormField.createSignature(stamper.getWriter());
field.setFieldName(name);
field.setFlags(PdfAnnotation.FLAGS_PRINT);
cell.setCellEvent(new MySignatureFieldEvent(field, stamper, pageNumber));
return cell;
}
public static class MySignatureFieldEvent implements PdfPCellEvent {
public PdfFormField field;
public PdfStamper stamper;
public int pageField;
public MySignatureFieldEvent(PdfFormField field, PdfStamper stamper, int pageField) {
this.field = field;
this.stamper = stamper;
this.pageField = pageField;
}
public void cellLayout(PdfPCell cell, Rectangle position, PdfContentByte[] canvases) {
PdfWriter writer = canvases[0].getPdfWriter();
field.setPage();
field.setWidget(position, PdfAnnotation.HIGHLIGHT_INVERT);
if (stamper == null) {
writer.addAnnotation(field);
}else {
stamper.addAnnotation(field, pageField);
}
}
}
首先,最初Adobe解释了此答案中描述的认证级别。特别是,如果文档具有证书签名,则禁止添加新字段,甚至是签名字段。
这种严格性似乎已经丢失,但在更新后的任何时候都可能再次适用,特别是在最近发布的针对pdf-insecurity.org的认证攻击利用了这种宽松的选择之后。
尽管如此,您永远不允许更改静态页面内容!但是,在代码中,可以通过向文档中添加一个表(包含事件)来添加其他签名字段。此表将更改静态页面内容。
因此,尝试只添加一个新的签名字段。
使用itext v5对文档进行数字签名时。5.11 PDF/A-2b文档被破坏——这意味着它们不再作为PDF/A文档有效。违反以下规则:https://github.com/veraPDF/veraPDF-validation-profiles/wiki/PDFA-Parts-2-and-3-rules#rule-643-1 在上面的链接中,它指定摘要无效,因此我也给你一个代码段,在使用iText
问题内容: 在PDFBox 2.x中,我将字典放入签名字段: 然后,我签名区域: 除了我在Adobe Acrobat中打开签名的文档时,它抱怨文档的内容已更改,一切看起来都还不错。如果我不添加字典,一切都很好。 任何人都知道哪里错了吗? 问题答案: 问题在于PDFBox签名没有考虑 Lock 字典。 根据ISO 32000-1(以及类似的ISO 32000-2): 12.8.2.4 FieldMD
我必须创建一个PDF文件,其中我使用PDFmake来实现这一点。但PDFmake不具备在文档中添加签名字段的功能。 因此,我打开了一个带有签名字段的示例PDF,并复制了包含签名字段名称的对象:
我希望我们拥有iPhone的员工能够让公众对一份文件进行数字签名,该文件最初是基于Spring的网络表单。 此表格仅在我们的内部网上,仅由我们的员工填写。 到目前为止,我的研究考虑了一些可能的解决方案: 手机屏幕上的电子湿墨水签名 目的是证明某个人签署了一份文件。 1从技术上讲是可能的,但并不重要,因为它很容易被复制。 是否可以在iOS设备上通过浏览器和javascript捕获指纹? 3能适应这种
我一直在网络和java教程中寻找这一点。但我没有澄清如何为文档生成数字签名。java教程对此进行了解释,但我真正想要的是 用户附带一个文件和一个字符串密钥 使用该密钥,文件被数字签名 相应的公钥,该标志与该文档一起发布 那么,如何转换给定的String私钥来做到这一点。在尝试java教程和web中给出的示例(带有将字节从字符串而不是文件中放置的一些变体)时,我遇到了如下异常
对于一个关于签名数据被哈希两次的C#问题,我看到了一个类似的答复,但是我不知道为什么我的签名数据会出现在这里。 C#PKCS7 Smartchard数字签名损坏