I'm using iText's methods to sign a PDF with digital certificate, generating a signature visible in the document with PdfSignatureAppearance, but I'd like the visible signature not to come out in print. I saw that there is something similar in the PdfAnnotation class, where you can add a flag for this. Is there any way to do this with the digital signature?
My code:
PdfStamper stp = null;
try {
PdfReader reader = new PdfReader(pdfInputFileName);
stp = PdfStamper.createSignature(reader, fout, '\0');
PdfSignatureAppearance sap = stp.getSignatureAppearance();
sap.setCrypto(privateKey, certificateChain, null, PdfSignatureAppearance.WINCER_SIGNED);
sap.setReason(reason);
sap.setLocation(location);
sap.setCertificationLevel(PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED);
sap.setVisibleSignature(new Rectangle(30, 830, 170, 770), 1, null);
stp.close();
} catch (DocumentException | IOException e) {
logger.error("An unknown error accoured while signing the PDF file: " + e.getMessage());
}
This is the link to a PDF signed by this code, when I print it, the signature stamp always comes out in the print:
https://s3.amazonaws.com/gxzadminlocal/anexo_28276.pdf
Current iText 5 versions automatically set the PRINT flag if the signature field does not yet exist and they have to create it. But if you fill an existing signature field, that flag is untouched. Thus, you might want to add an empty signature field without that PRINT flag in a first step and in a second step sign using that field, e.g. with the following code:
Preparing the PDF
You can prepare a PDF with an empty signature field without setting the PRINT flag like this:
try ( InputStream resource = SOURCE_STREAM;
OutputStream os = INTERMEDIATE_OUTPUT_STREAM) {
PdfReader reader = new PdfReader(resource);
PdfStamper stamper = new PdfStamper(reader, os);
PdfFormField field = PdfFormField.createSignature(stamper.getWriter());
field.setFieldName("Signature");
field.setWidget(new Rectangle(30, 830, 170, 770), PdfAnnotation.HIGHLIGHT_NONE);
stamper.addAnnotation(field, 1);
stamper.close();
}
(CreateSignature test signWidgetNoPrint pass 1)
In particular you don't do
field.setFlags(PdfAnnotation.FLAGS_PRINT);
here!
Signing the prepared PDF
Having created that intermediate PDF, you can sign it like this:
try ( InputStream resource = INTERMEDIATE_INPUT_STREAM;
OutputStream os = RESULT_STREAM) {
PdfReader reader = new PdfReader(resource);
PdfStamper stamper = PdfStamper.createSignature(reader, os, '\0');
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
appearance.setReason("reason");
appearance.setLocation("location");
appearance.setCertificationLevel(PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED);
appearance.setVisibleSignature("Signature");
ExternalSignature pks = new PrivateKeySignature(pk, "SHA512", "BC");
ExternalDigest digest = new BouncyCastleDigest();
MakeSignature.signDetached(appearance, digest, pks, chain, null, null, null, 0, CryptoStandard.CMS);
}
(CreateSignature test signWidgetNoPrint pass 2)
assuming you have prepared your private key in pk and your certificate chain in chain; and assuming you have registered Bouncy Castle as security provider.
In the result PDF the signature visualization appears on screen but not in print.
Related
I am trying to add a border to a digital signature on a .pdf, however if more than one sign the first signature is invalidated and the second signature is valid, I suppose this happens because I am using PdfCanvas, for now I have not found another way to achieve it.
PdfSigner signer = new PdfSigner(pdfReader, byteNewPdf_2, new StampingProperties().useAppendMode());
PdfSignatureAppearance appearance = createAppearance(signer, reasonCert, reasonPDF, location,
signBoxData, originalHashPdf, certData, metadata, pdfImage, idProcess, nameSigner, signerNumber, signatureDate, isDomain);
Rectangle rect = signatureApp.appearanceRectangleSign(possitionResponse, metadata, signBoxData, signerNumber);
appearance.setPageRect(rect).setPageNumber(possitionResponse.getPageNumber());
IExternalDigest digest = new BouncyCastleDigest();
IExternalSignature signature = new PrivateKeySignature(pk, digestAlgorithm, provider);
signer.signDetached(digest, signature, chain, null, null, null, 0 , subfilter);
I have read that the borders must be controlled with PdfCanvas, however I have the problem that the first signature is invalidated.
PdfCanvas canvas = signatureApp.borderSignPdf(signer, idProcess, signBoxData, possitionResponse,
metadata, signerNumber);
In response to mkl's comment, I am sharing the fragment where the canvas is created, I make a return because it was generated within a method:
return new PdfCanvas(signer.getDocument(), possitionResponse.getPageNumber())
.rectangle(possitionResponse.getX(), possitionResponse.getY(), signBoxData.getSignatureBoxWidth(), signBoxData.getSignatureBoxHeight())
.setLineWidth(!view_BorderSign ? 0 : 1) .stroke();
The document is successfully signed with n signature numbers without a problem as long as the canvas is not placed, the signatures are not invalidated but as soon as I add the canvas the first signature is invalidated, maybe as they commented it is the way I create the signature.
PdfSigner signer = new PdfSigner(pdfReader, byteNewPdf_2, new
StampingProperties().useAppendMode());
I'm trying to use Aspose pdf java to digitally sign a document. This is my code
public ByteArrayOutputStream signDocument(Document doc, String signedBy) throws Exception {
PdfFileSignature pdfSignSingle = new PdfFileSignature();
pdfSignSingle.bindPdf(doc);
pdfSignSingle.setCertificate(prop.getSigningKeyStorePath(), prop.getKeystorePassword());
PKCS7 signature = new PKCS7(prop.getSigningKeyStorePath(), prop.getKeystorePassword());
pdfSignSingle.setSignatureAppearance(prop.getSimploudLogo());
signature.setAuthority("Authority");
signature.setDate(new Date());
signature.setContactInfo("email");
signature.setLocation("Location");
signature.setReason("reason");
pdfSignSingle.sign(1, true, new java.awt.Rectangle(100, 100, 200, 200), signature);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
pdfSignSingle.save(baos);
pdfSignSingle.dispose();
doc.dispose();
return baos;
}
In picture is shown how the signature looks in adobeReader.
As you can see both image and Authority are not shown. I tried image to be both in pdf and png format. I've also tried to make it smaller then Rectangle area. As for authority i really need it to be customizable so that text in first line in picture can be
Signed by "customParameter"
The API offers another Class i.e. SignatureCustomAppearance which can further be used to set such properties for a signature such as DateSigned, Reason, Location, etc. Please check the following complete code snippet which can fulfill your requirements:
String inputFile = "doc.pdf";
String outSignedFile = "out_20.9.pdf";
// Create PdfFileSignature instance
com.aspose.pdf.facades.PdfFileSignature pdfSignSingle = new com.aspose.pdf.facades.PdfFileSignature();
// Bind the source PDF by reading contents of Stream
pdfSignSingle.bindPdf(inputFile);
PKCS7 pkcs = new PKCS7("mykey2.pfx", "pass");
pkcs.setAuthority("Authority");
pkcs.setDate(new Date());
pkcs.setContactInfo("email");
pkcs.setLocation("Location");
pkcs.setReason("reason");
pkcs.setImage(new FileInputStream("simpleLogo.png"));
SignatureCustomAppearance sca = new SignatureCustomAppearance();
sca.setDateSignedAtLabel(null);
sca.setDigitalSignedLabel(null);
sca.setShowReason(true);
sca.setShowLocation(true);
sca.setShowContactInfo(true);
pkcs.setCustomAppearance(sca);
pdfSignSingle.sign(1, true, new java.awt.Rectangle(100, 100, 200, 200), pkcs);
// Set image for signature appearance
//pdfSignSingle.setSignatureAppearance("simpleLogo.png");
// Save final output
pdfSignSingle.save(outSignedFile);
As mentioned in the comment below the question, the same inquiry was posted in Aspose.PDF official forum as well at "Aspose pdf java PdfFileSignature setAuthority not working" and a solution has also been provided there.
My project digitally signs PDF documents using a digital certificate and displays the signature at the bottom left of the document. It had always worked well until now, there are some documents that are digitally signed but it is not shown although it is recognized that the rectangle where it is visible is. Could someone help with this, I leave a snippet that handles the digital signature.
Here I leave a screenshot of how the digital signature is in a PDF document, I show it in the lower left corner of the document.
EXAMPLES
In this link I have shared example pdf documents of the problem and without it, I will detail them below:
ok_unsigned.pdf file: it is a document that when passing through my project is signed well, becoming the ok_signed.pdf file (this is the norm so far)
ok_signed.pdf file: it is the digitally signed ok_unsigned.pdf file, it is a case of success
bad_unsigned.pdf file: it is a blank document that when digitally signed (bad_signed.pdf) the issue in question appears
bad_signed.pdf file: it is a document with the issue in question, digitally signed but without visually seeing the signature field.
CODE
try {
BouncyCastleProvider providerBC = new BouncyCastleProvider();
Security.addProvider(providerBC);
KeyStore ks = KeyStore.getInstance("pkcs12");
ks.load(new FileInputStream(keystore), password);
String alias = ks.aliases().nextElement();
Certificate[] chain = ks.getCertificateChain(alias);
PrivateKey pk = (PrivateKey) ks.getKey(alias, password);
PdfReader reader = new PdfReader(src);
FileOutputStream fos = new FileOutputStream(new File(dest));
PdfSigner signer = new PdfSigner(reader, fos, new StampingProperties());
Rectangle rect = new Rectangle(10, 10, 150, 50);
PdfSignatureAppearance appearance = signer.getSignatureAppearance();
appearance.setPageRect(rect)
.setCertificate(chain[0])
.setReasonCaption("")
.setLocationCaption("")
.setSignatureCreator("SignerJAGC - iText 7.1.11")
.setPageNumber(1);
signer.setFieldName("Banca en Línea - Envío de Documentos");
signer.setSignDate(new GregorianCalendar());
signer.setCertificationLevel(PdfSigner.CERTIFIED_NO_CHANGES_ALLOWED);
IExternalDigest digest = new BouncyCastleDigest();
IExternalSignature signature = new PrivateKeySignature(pk, DigestAlgorithms.SHA256, providerBC.getName());
signer.signDetached(digest, signature, chain, null, null, null, 0, SUBFILTER);
System.out.println("SIGNED");
} catch (Exception ex) {
System.err.println(ex.getMessage());
}
}```
As #mkl said your coordinates might be outside of the page visible area as not all PDF pages have the bottom left corner at (0, 0).
Try creating the signature rectangle like this:
Rectangle rect = new Rectangle(
yourPageCropBoxLowerLeftX + 10,
yourPageCropBoxLowerLeftY + 10,
yourPageCropBoxLowerLeftX + 10 + yourSignatureWidth,
yourPageCropBoxLowerLeftY + 10 + yourSignatureHeight);
You just have to see how you can read the page's crop box coordinates, the lower left corner, as I'm not familiar with iText API.
I created code that adds an image to an existing pdf document and then signs it, all using PDFBox (see code below).
The code nicely adds the image and the signature. However, in some documents, Acrobat Reader complains that "The signature byte range is invalid."
The problem seems to be the same as the problem described in this question. The answer to that question describes the problem in more detail: the problem is that my code leaves a mix of cross reference types in the document (streams and tables). Indeed, some documents won't even open because of the problems that this creates.
My question is: how do I prevent this? How do I add an image to an existing pdf document without creating multiple cross reference types?
public class TC3 implements SignatureInterface{
private char[] pin = "123456".toCharArray();
private BouncyCastleProvider provider = new BouncyCastleProvider();
private PrivateKey privKey;
private Certificate[] cert;
public TC3() throws Exception{
Security.addProvider(provider);
KeyStore keystore = KeyStore.getInstance("PKCS12", provider);
keystore.load(new FileInputStream(new File("resources/IIS_keystore.pfx")), pin.clone());
String alias = keystore.aliases().nextElement();
privKey = (PrivateKey) keystore.getKey(alias, pin);
cert = keystore.getCertificateChain(alias);
}
public void doSign() throws Exception{
byte inputBytes[] = IOUtils.toByteArray(new FileInputStream("resources/rooster.pdf"));
PDDocument pdDocument = PDDocument.load(new ByteArrayInputStream(inputBytes));
PDJpeg ximage = new PDJpeg(pdDocument, ImageIO.read(new File("resources/logo.jpg")));
PDPage page = (PDPage)pdDocument.getDocumentCatalog().getAllPages().get(0);
PDPageContentStream contentStream = new PDPageContentStream(pdDocument, page, true, true);
contentStream.drawXObject(ximage, 50, 50, 356, 40);
contentStream.close();
ByteArrayOutputStream os = new ByteArrayOutputStream();
pdDocument.save(os);
os.flush();
pdDocument.close();
inputBytes = os.toByteArray();
pdDocument = PDDocument.load(new ByteArrayInputStream(inputBytes));
PDSignature signature = new PDSignature();
signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
signature.setName("signer name");
signature.setLocation("signer location");
signature.setReason("reason for signature");
signature.setSignDate(Calendar.getInstance());
pdDocument.addSignature(signature, this);
File outputDocument = new File("resources/signed.pdf");
ByteArrayInputStream fis = new ByteArrayInputStream(inputBytes);
FileOutputStream fos = new FileOutputStream(outputDocument);
byte[] buffer = new byte[8 * 1024];
int c;
while ((c = fis.read(buffer)) != -1)
{
fos.write(buffer, 0, c);
}
fis.close();
FileInputStream is = new FileInputStream(outputDocument);
pdDocument.saveIncremental(is, fos);
pdDocument.close();
}
public byte[] sign(InputStream content) {
CMSProcessableInputStream input = new CMSProcessableInputStream(content);
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
List<Certificate> certList = Arrays.asList(cert);
CertStore certStore = null;
try{
certStore = CertStore.getInstance("Collection", new CollectionCertStoreParameters(certList), provider);
gen.addSigner(privKey, (X509Certificate) certList.get(0), CMSSignedGenerator.DIGEST_SHA256);
gen.addCertificatesAndCRLs(certStore);
CMSSignedData signedData = gen.generate(input, false, provider);
return signedData.getEncoded();
}catch (Exception e){}
return null;
}
public static void main(String[] args) throws Exception {
new TC3().doSign();
}
The issue
As had already been explained in this answer, the issue at work here is that
when non-incrementally storing the document with the added image, PDFBox 1.8.9 does so using a cross reference table no matter if the original file used a table or stream; if the original file used a stream, the cross reference stream dictionary entries are copied into the trailer dictionary;
...
0000033667 00000 n
0000033731 00000 n
trailer
<<
/DecodeParms <<
/Columns 4
/Predictor 12
>>
/Filter /FlateDecode
/ID [<5BD95916CAE5E84E9D964396022CBDCD> <6420B4547602C943AF37DD6C77496BE8>]
/Info 6 0 R
/Length 61
/Root 1 0 R
/Size 35
/Type /XRef
/W [1 2 1]
/Index [20 22]
>>
startxref
35917
%%EOF
(Most of these trailer entries here are useless or even misleading, see below.)
when incrementally saving the signature, COSWriter.doWriteXRefInc uses COSDocument.isXRefStream to determine whether the existing document (the one we stored as above) uses a cross reference stream. As mentioned above, it does not. Unfortunately, though, COSDocument.isXRefStream in PDFBox 1.8.9 is implemented as
public boolean isXRefStream()
{
if (trailer != null)
{
return COSName.XREF.equals(trailer.getItem(COSName.TYPE));
}
return false;
}
Thus, the misleading trailer entry Type shown above make PDFBox think it has to use a cross reference stream.
The result is a document whose initial revision ends with a cross reference table and weird trailer entries and whose second revision ends with a cross reference stream. This is not valid.
A work-around
Fortunately, though, understanding how the issue arises presents a work-around: Removing the troublesome trailer entry, e.g. like this:
inputBytes = os.toByteArray();
pdDocument = PDDocument.load(new ByteArrayInputStream(inputBytes));
pdDocument.getDocument().getTrailer().removeItem(COSName.TYPE); // <<<<<<<<<< Remove misleading entry <<<<<<<<<<
With this work-around both revisions in the signed document use cross reference tables and the signature is valid.
Beware, if upcoming PDFBox versions change to save documents loaded from sources with cross reference streams using xref streams, too, the work-around must again be removed.
I would assume, though, that won't happen in the 1.x.x versions to come, and version 2.0.0 will introduce a fundamentally changed API, so the original code won't work out-of-the-box then anyhow.
Other ideas
I tried other ways, too, to circumvent this problem, trying to
store the first manipulation as incremental update, too, or
add the image during the same incremental update as the signature,
cf. SignLikeUnOriginalToo.java, but failed. PDFBox 1.8.9 incremental updates only seem to properly work for adding signatures.
Other ideas revisited
After looking into the creation of additional revisions using PDFBox some more, I tried the other ideas again and now succeeded!
The crucial part is to mark the added and changed objects as updated, including a path from the document catalog.
Applying the first idea (adding the image as an explicit intermediate revision) amounts to this change in doSign:
...
FileOutputStream fos = new FileOutputStream(intermediateDocument);
FileInputStream fis = new FileInputStream(intermediateDocument);
byte inputBytes[] = IOUtils.toByteArray(inputStream);
PDDocument pdDocument = PDDocument.load(new ByteArrayInputStream(inputBytes));
PDJpeg ximage = new PDJpeg(pdDocument, ImageIO.read(logoStream));
PDPage page = (PDPage) pdDocument.getDocumentCatalog().getAllPages().get(0);
PDPageContentStream contentStream = new PDPageContentStream(pdDocument, page, true, true);
contentStream.drawXObject(ximage, 50, 50, 356, 40);
contentStream.close();
pdDocument.getDocumentCatalog().getCOSObject().setNeedToBeUpdate(true);
pdDocument.getDocumentCatalog().getPages().getCOSObject().setNeedToBeUpdate(true);
page.getCOSObject().setNeedToBeUpdate(true);
page.getResources().getCOSObject().setNeedToBeUpdate(true);
page.getResources().getCOSDictionary().getDictionaryObject(COSName.XOBJECT).setNeedToBeUpdate(true);
ximage.getCOSObject().setNeedToBeUpdate(true);
fos.write(inputBytes);
pdDocument.saveIncremental(fis, fos);
pdDocument.close();
pdDocument = PDDocument.load(intermediateDocument);
PDSignature signature = new PDSignature();
...
(as in SignLikeUnOriginalToo.java method doSignTwoRevisions)
Applying the second idea (adding the image as part of the signing revision) amounts to this change in doSign:
...
byte inputBytes[] = IOUtils.toByteArray(inputStream);
PDDocument pdDocument = PDDocument.load(new ByteArrayInputStream(inputBytes));
PDJpeg ximage = new PDJpeg(pdDocument, ImageIO.read(logoStream));
PDPage page = (PDPage) pdDocument.getDocumentCatalog().getAllPages().get(0);
PDPageContentStream contentStream = new PDPageContentStream(pdDocument, page, true, true);
contentStream.drawXObject(ximage, 50, 50, 356, 40);
contentStream.close();
page.getResources().getCOSObject().setNeedToBeUpdate(true);
page.getResources().getCOSDictionary().getDictionaryObject(COSName.XOBJECT).setNeedToBeUpdate(true);
ximage.getCOSObject().setNeedToBeUpdate(true);
PDSignature signature = new PDSignature();
...
(as in SignLikeUnOriginalToo.java method doSignOneStep)
Both variants are clearly preferable to the original approach.
I am writting in reference to an error I get while a signing a concrete pdf file with Java iText libraries version 5.5.3.
Adobe Pro verifies the conformance and tell me that conformance has been verified but iText tell me that "Annotation of type /Widget should have Contents key".
You can get the concrete file at http://www.boe.es/boe/dias/2014/08/06/pdfs/BOE-A-2014-8500.pdf
I post below the trace I get:
Caused by: com.itextpdf.text.pdf.PdfAConformanceException: Annotation of type /Widget should have Contents key.
at com.itextpdf.text.pdf.internal.PdfA1Checker.checkAnnotation(PdfA1Checker.java:462)
at com.itextpdf.text.pdf.internal.PdfAChecker.checkPdfAConformance(PdfAChecker.java:219)
at com.itextpdf.text.pdf.internal.PdfAConformanceImp.checkPdfIsoConformance(PdfAConformanceImp.java:71)
at com.itextpdf.text.pdf.PdfWriter.checkPdfIsoConformance(PdfWriter.java:3426)
at com.itextpdf.text.pdf.PdfWriter.checkPdfIsoConformance(PdfWriter.java:3422)
at com.itextpdf.text.pdf.PdfAnnotation.toPdf(PdfAnnotation.java:999)
at com.itextpdf.text.pdf.PdfIndirectObject.writeTo(PdfIndirectObject.java:158)
at com.itextpdf.text.pdf.PdfWriter$PdfBody.write(PdfWriter.java:420)
at com.itextpdf.text.pdf.PdfWriter$PdfBody.add(PdfWriter.java:398)
at com.itextpdf.text.pdf.PdfWriter$PdfBody.add(PdfWriter.java:373)
at com.itextpdf.text.pdf.PdfWriter$PdfBody.add(PdfWriter.java:369)
at com.itextpdf.text.pdf.PdfWriter.addToBody(PdfWriter.java:843)
at com.itextpdf.text.pdf.PdfStamperImp.addAnnotation(PdfStamperImp.java:1389)
at com.itextpdf.text.pdf.PdfStamperImp.addAnnotation(PdfStamperImp.java:1401)
at com.itextpdf.text.pdf.PdfSignatureAppearance.preClose(PdfSignatureAppearance.java:1283)
Thanks in advance for any reply.
I have tried applied sample from here (http://itextpdf.com/book/digitalsignatures20130304.pdf, page 29) to your PDF document and everything seems to work fine.
The only exceptions in code are:
Create PdfAStamper instead of PdfStamper:
PdfAStamper stamper = PdfAStamper.createSignature(reader, os, '\0', PdfAConformanceLevel.PDF_A_1B);
Set font to PdfSignatureAppearance so that font can be embedded:
appearance.setLayer2Font(FontFactory.getFont("FreeMonoBold.ttf", BaseFont.WINANSI, BaseFont.EMBEDDED, 12));
Set output intents:
ICC_Profile icc = ICC_Profile.getInstance(new FileInputStream("sRGB Color Space Profile.icm"));
stamper.getWriter().setOutputIntents("Custom", "", "http://www.color.org", "sRGB IEC61966-2.1", icc);
Here's a complete code:
static public void sign(String src, String dest, Certificate[] chain, PrivateKey pk, String digestAlgorithm, String provider, MakeSignature.CryptoStandard subfilter, String reason, String location) throws GeneralSecurityException, IOException, DocumentException {
// Creating the reader and the stamper
PdfReader reader = new PdfReader(src);
FileOutputStream os = new FileOutputStream(dest);
PdfAStamper stamper = PdfAStamper.createSignature(reader, os, '\0', PdfAConformanceLevel.PDF_A_1B);
// Creating the appearance
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
appearance.setLayer2Font(FontFactory.getFont("FreeMonoBold.ttf", BaseFont.WINANSI, BaseFont.EMBEDDED, 12));
appearance.setReason(reason);
appearance.setLocation(location);
appearance.setVisibleSignature(new Rectangle(36, 748, 144, 780), 1, "sig");
ICC_Profile icc = ICC_Profile.getInstance(new FileInputStream("sRGB Color Space Profile.icm"));
stamper.getWriter().setOutputIntents("Custom", "", "http://www.color.org", "sRGB IEC61966-2.1", icc);
// Creating the signature
ExternalDigest digest = new BouncyCastleDigest();
ExternalSignature signature = new PrivateKeySignature(pk, digestAlgorithm, provider);
MakeSignature.signDetached(appearance, digest, signature, chain, null, null, null, 0, subfilter);
}
But probably your use case is a little bit different. Maybe you set special options or something... Can you please provide the sample code which fails?