[英]Multiple signatures with Itext - issue with existing signature blocks
所以我目前正在一個項目上簽名PDF文檔,我遇到了一個問題。 因此,如果我指定要簽名的塊,則我可以正確簽名文檔而不會出現任何問題,即
signatureAppearance.setVisibleSignature(矩形,頁面,signingBlockName);
我可以毫無問題地添加多個簽名,並且所有簽名仍然有效。 現在,我更改了代碼,首先添加空的簽名塊,然后使用我添加的簽名塊名稱對這些塊進行簽名,即
signatureAppearance.setVisibleSignature(signingBlockName);
這樣做的原因是,我們將生成帶有已經存在簽名字段(可以有多個)的PDF文檔,但是這里的問題是,由於我已經開始使用這種對簽名塊進行簽名的方法,因此第二個簽名會使即使簽名是在附加模式下完成的,也只有第一個簽名,並且只有最后一個簽名具有PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED認證級別。
我是否缺少某些東西?舊版本的IText是否存在錯誤? 我目前正在使用IText版本4.2.0。
提前致謝。
- - - 碼 - - - - - - -
/**
* The method is used to sign a pdf document using a certificate, image of the signature as well as specified signature blocks
*
* @param document the document to be signed
* @param certificate the certificate to be used to do the signing
* @param signature the image of the signature used to sign the document
* @param signatureBlocks the blocks in the pdf document to sign
* @return the signed document bytes
* @throws Exception
*/
public byte[] signPDFDocument(PDFDocument document, PKSCertificate certificate, SignatureImage signature, List<SigningBlock> signatureBlocks, boolean certifyDocument) throws Exception
{
document.addSignatureBlocks(signatureBlocks);
PDFDocument signedDocument = signPDFDocumentSignatureBlocks(document, certificate, signature, signatureBlocks, certifyDocument);
return signedDocument.getDocumentBytes();
}
/**
* The method is used to sign a pdf document using a certificate, image of the signature as well as specified signature blocks
*
* @param document the document to be signed
* @param certificate the certificate to be used to do the signing
* @param signatureBlocks the blocks in the pdf document to sign
* @return the signed document bytes
* @throws Exception
*/
public byte[] signPDFDocument(PDFDocument document, PKSCertificate certificate, List<SigningBlock> signatureBlocks, boolean certifyDocument) throws Exception
{
document.addSignatureBlocks(signatureBlocks);
PDFDocument signedDocument = signPDFDocumentSignatureBlocks(document, certificate, null, signatureBlocks, certifyDocument);
return signedDocument.getDocumentBytes();
}
/**
* The method is used to get the names of all signature fields
*
* @param document the document to check for the signature fields
* @return the list of signature field names used and unused
* @throws Exception
*/
public List<String> getAvailableSignatureBlocks(PDFDocument document) throws Exception
{
PdfReader reader = new PdfReader(document.getDocumentBytes());
ArrayList arrayList = reader.getAcroFields().getBlankSignatureNames();
return Arrays.asList((String[]) arrayList.toArray(new String[arrayList.size()]));
}
/**
* This method is used to loop over the signature blocks and sign them
*
* @param document the document to be signed
* @param certificate the certificate to apply to the signature
* @param signature the image of the client signature
* @param signatureBlocks the signature blocks to create and sign
* @param certifyDocument flag to indicate if the document should be signed
* @throws Exception
*/
private PDFDocument signPDFDocumentSignatureBlocks(PDFDocument document, PKSCertificate certificate, SignatureImage signature, List<SigningBlock> signatureBlocks, boolean certifyDocument) throws Exception
{
for (int i = 0; i < signatureBlocks.size();i++)
{
PDFDocument signedDocument = new PDFDocument(new ByteArrayOutputStream());
PdfReader reader = new PdfReader(document.getDocumentBytes());
PdfStamper stamper = createPDFStamper(signedDocument, reader);
document = signPDFDocumentSignatureBlock(signedDocument, certificate, signature, signatureBlocks.get(i), stamper, certifyDocument && i == (signatureBlocks.size() - 1));
}
return document;
}
/**
* The method is used to sign the pdf document, it also marks the signing process if it is the final signature process
*
* @param signedDocument the signed document to be generated
* @param certificate the certificate to be used to do the signing
* @param signature the image of the signature used to sign the document
* @param signingBlock the current block to sign in the document
* @param stamper the current document stamper reference
* @param certifyDocument indicate if this signing should certify the document
* @return the signed document object
* @throws Exception
*/
private PDFDocument signPDFDocumentSignatureBlock(PDFDocument signedDocument, PKSCertificate certificate, SignatureImage signature, SigningBlock signingBlock, PdfStamper stamper, boolean certifyDocument) throws Exception
{
PdfSignatureAppearance appearance = signWithSignatureImage(stamper, signature, signingBlock.getName(), certifyDocument);
signWithCertificate(certificate, appearance);
signWithTimeStampingAuthority(appearance, certificate);
signedDocument.updateBytesFromByteStream();
return signedDocument;
}
/**
* The method is used to get the instance of the PDF stamper to stamp the document
*
* @param signedDocument the document that is currently being signed
* @param reader the reader that is reading the document to sign.
* @return the stamper instance
* @throws Exception
*/
private PdfStamper createPDFStamper(PDFDocument signedDocument, PdfReader reader) throws Exception
{
return PdfStamper.createSignature(reader, signedDocument.getByteStream(), '\0', null, true);
}
/**
* The method is used to add the signature image to the signing block
*
* @param stamper the current pdf stamping reference
* @param signature the image to apply to the stamper
* @param signingBlockName the block to sign
* @param certifyDocument indicate if this signing should certify the document
* @throws Exception
*/
private PdfSignatureAppearance signWithSignatureImage(PdfStamper stamper, SignatureImage signature, String signingBlockName, boolean certifyDocument) throws Exception
{
PdfSignatureAppearance signatureAppearance = stamper.getSignatureAppearance();
signatureAppearance.setVisibleSignature(signingBlockName);
setImageForSignature(signatureAppearance, signature);
certifyDocumentSignature(signatureAppearance, certifyDocument);
return signatureAppearance;
}
/**
* The method is used to add an image to the signature block
*
* @param signatureAppearance the reference to the current document appearance
* @param signature the image to apply to the signature
* @throws Exception
*/
private void setImageForSignature(PdfSignatureAppearance signatureAppearance, SignatureImage signature) throws Exception
{
if(signature != null)
{
signatureAppearance.setImage(Image.getInstance(signature.getSignatureImage(), null));
}
}
/**
* The method is used to mark the signature as the certification signature
*
* @param signatureAppearance the reference to the current document appearance
* @param certifyDocument indicates if the document should be certified
*/
private void certifyDocumentSignature(PdfSignatureAppearance signatureAppearance, boolean certifyDocument)
{
if(certifyDocument)
{
signatureAppearance.setCertificationLevel(PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED);
}
}
/**
* The method is used to add the text containing information about the certificate to the signing appearance
*
* @param certificate the certificate to be used to do the signing
* @param signatureAppearance the appearance of the signature on the document
* @throws Exception
*/
private void signWithCertificate(PKSCertificate certificate, PdfSignatureAppearance signatureAppearance) throws Exception
{
signatureAppearance.setLayer2Text(buildTextSignature(certificate));
signatureAppearance.setLayer2Font(new Font(Font.COURIER, 9));
signatureAppearance.setAcro6Layers(true);
signatureAppearance.setRender(PdfSignatureAppearance.SignatureRenderDescription);
}
/**
* The method is used to encrypt the document using the certificate as well as a timestamping authority
*
* @param appearance the appearance of the signature on the document
* @param certificate the certificate to be used to do the signing
* @throws Exception
*/
private void signWithTimeStampingAuthority(PdfSignatureAppearance appearance, PKSCertificate certificate) throws Exception
{
_timestampingService.signWithTimestampingAuthority(appearance, certificate);
}
/**
* The method builds the text that is used in the text representation of the signature
*
* @param certificate the certificate to be used to do the signing
* @return the text representation of certificate information
* @throws Exception
*/
private String buildTextSignature(PKSCertificate certificate) throws Exception
{
String organization = certificate.getCertificateFieldByName("O");
String commonName = certificate.getCertificateFieldByName("CN");
String signDate = new SimpleDateFormat(_datetimeFormat).format(Calendar.getInstance().getTime());
String expirationDate = new SimpleDateFormat(_datetimeFormat).format(((X509Certificate)certificate.getCertificateChain()[0]).getNotAfter());
return "Digitally signed by " + organization + "\nSignee: " + commonName + "\nSign date: " + signDate + "\n" + "Expiration date: " + expirationDate;
}
即使簽名是在追加模式下完成的,第二個簽名也會使第一個簽名無效,並且只有最后一個簽名具有
PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED
認證級別。
正如最初評論中所推測的那樣,這實際上是個問題: 只有文檔的第一個簽名可以是認證簽名。 以后的簽名只能(PDF 2.0)使用簽名字段鎖定條目來限制訪問權限,請參見。 規格:
PDF文檔可能包含[...]
最多一個認證簽名(PDF 1.5)。 [...]簽名字典應包含具有DocMDP轉換方法的簽名參考字典(請參見表253)。 [...]
一個文檔只能包含一個包含DocMDP轉換方法的簽名字段。 它應該是文檔中第一個簽名的字段。
( ISO 32000-1第12.8.1和12.8.2.2.1節)
在即將發布的PDF標准2.0版(ISO-32000-2)中,可以使用簽名字段鎖定條目來限制訪問權限:
P號(可選; PDF 2.0)為此文件授予的訪問權限。 有效值如下:
1不允許對文檔進行任何更改; 對文檔的任何更改都會使簽名無效。
2個允許的更改包括填寫表單,實例化頁面模板和簽名; 其他更改會使簽名無效。
3個允許的更改與第2個相同,以及注釋的創建,刪除和修改; 其他更改會使簽名無效。
沒有默認值; 缺少此密鑰將不會影響簽名驗證規則。
如果MDP權限已經從較早的增量保存部分或文檔的原始部分生效,則該數字將指定的權限小於或等於基於文檔較早版本的簽名已經生效的權限。 即,可以拒絕但不能添加權限。 如果該數字指定的權限大於已生效的MDP值,則新數字將被忽略。
如果文檔沒有作者簽名,則有效的初始權限是基於數字3的那些權限。
新權限適用於此密鑰作為簽名之后的對文檔的任何增量更改。
(PDF 2.0草案的表“簽名域鎖定字典中的條目”)
Adobe Reader / Acrobat和iText(自5.3'ish版本的IIRC起)都已經支持此功能,並且似乎允許OP產生效果,即不允許在最終簽名字段之后進行任何更改。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.