【发布时间】:2021-05-27 17:05:31
【问题描述】:
我正在尝试将一个空的签名字段添加到现有的数字签名 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);
}
}
}
【问题讨论】:
-
根据您阅读 pdf 规范的方式,不允许向经过认证的 pdf 添加新的签名字段。
-
我用过PdfSignatureAppearance.CERTIFIED_FORM_FILLING_AND_ANNOTATIONS,所以我认为这个级别允许添加签名字段对吗?但我的问题是,我应该如何将 pdf 变红并添加字段而不破坏它?
标签: java itext digital-signature