I'm using iTextSharp 5.5.2 and I want to certify an XFA document with a digital certificate.
My code looks as follows:
PdfReader reader = new PdfReader(path);
FileStream os = new FileStream(dest, FileMode.Create);
PdfStamper stamper = PdfStamper.createXmlSignature(reader, os);
XmlSignatureAppearance appearance = stamper.XmlSignatureAppearance;
appearance.SetXmlLocator(new XfaXmlLocator(stamper));
appearance.SetXpathConstructor(new XfaXpathConstructor(XfaXpathConstructor.XdpPackage.Datasets)); // Optional Line
appearance.SetCertificate(myCert);
var pk = Org.BouncyCastle.Security.DotNetUtilities.GetKeyPair(myCert.PrivateKey).Private;
IExternalSignature pks = new PrivateKeySignature(pk, DigestAlgorithms.SHA1);
MakeXmlSignature.SignXmlDSig(appearance, pks, GenerateKeyInfo(myChain));
Unfortunately, when I open the PDF after certification, it pops up that the "verify operation failed."
If I comment out the optional line related to the XfaXpathConstructor, I get the banner message along the type of the PDF that says that "At least one data signature is invalid".
Either way, I am unable to open the Signature Panel and the PDF does not successfully certify... What am I missing?
Related
Using the MySigner.SignedDeferred method (MySigner extends the PdfSigner class as described here) I am able to digitally sign the document with an invisible signature which Adobe Reader validates in the signature panel.
However, when I try to attach an in-document signature visualization to an existing signature as described here, The generated pdf then has a signature panel banner that reads "Signed and all signature are valid but with unsigned changes".
I find this perplexing since before signer.SignExternalContainer is called I do not change the Certification Level, thus ensuring it defaults and thus is Not at a level of certified with no changes allowed.
pdf screenshot in Adobe Reader
Also the original stamping properties to generate the unsigned PDF (source) uses AppendMode.
Here is the relevant C# code:
PdfReader readerPrepped2 = new PdfReader(pathDestination);
PdfWriter pdfWriter2 = new PdfWriter(pathDestination2);
PdfDocument pdfDocument = new PdfDocument(readerPrepped2, pdfWriter2, new StampingProperties().UseAppendMode());
SignatureUtil signatureUtil = new SignatureUtil(pdfDocument);
PdfAcroForm acroForm = PdfAcroForm.GetAcroForm(pdfDocument, false);
foreach (String name in signatureUtil.GetSignatureNames())
{
PdfPKCS7 pkcs7 = signatureUtil.ReadSignatureData(name);
X509Certificate signerCert = x509Certificate3;
String signerName = CertificateInfo.GetSubjectFields(signerCert).GetField("CN");
String issuer = CertificateInfo.GetIssuerFields(signerCert).GetField("CN");
var date = pkcs7.GetSignDate().ToString();
PdfFormField field = acroForm.GetField("Signature");
PdfFont font = PdfFontFactory.CreateFont(StandardFonts.HELVETICA);
field.SetFont(font);
field.SetFontSize(5);
field.SetModified();
field.SetVisibility(4);
foreach (PdfWidgetAnnotation pdfWidgetAnnotation in field.GetWidgets())
{
pdfWidgetAnnotation.SetRectangle(new PdfArray(new Rectangle(36, 348, 236, 428)));
// pdfWidgetAnnotation.SetColor([])
PdfFormXObject form = new PdfFormXObject(new Rectangle(200, 80));
// form.SetModified();
Canvas canvas = new Canvas(form, pdfDocument);
//canvas.SetStrokeColor(ColorConstants.RED);
canvas.SetFontSize(6);
canvas.Add(new Paragraph().SetItalic().Add("Signed by:"));
canvas.Add(new Paragraph()/*.SetBold()*/.Add(signerName));
canvas.Add(new Paragraph().SetItalic().Add("Date:"));
canvas.Add(new Paragraph()/*.SetBold()*/.Add(date));
canvas.Add(new Paragraph().SetItalic().Add("Issuer:"));
canvas.Add(new Paragraph()/*.SetBold()*/.Add(issuer));
pdfWidgetAnnotation.SetNormalAppearance(form.GetPdfObject());
}
}
pdfDocument.SetCloseWriter(true);
pdfDocument.SetCloseReader(false);
pdfDocument.Close();
I tried extending the PDFSigner class with a new class called MySigner so as to avoid adding entries to the structure tree. I also tried explcitly designating the CERTIFICATION_LEVEL as PdfSigner.CERTIFIED_FORM_FILLING_AND_ANNOTATIONS (to no avail as Adobe does not recognize the pdf as digitally signed at all in this scenario). I also tried toggling .SetModified on form and field.
I am working on converting a PDF to PDF/A.
I already did this conversion through a paid PDFTools library, the result of the conversion I place it on this page that is responsible for validating whether it complies with the PDA/A standard https://www.pdf-online.com/osa/validate.aspx
Validating indicates that it meets the standard.
Once converted, the PDF will be signed with the Itext 5.5.5 library, however, if I used the validator again the PDF/A standard is no longer valid. The errors shown on the validator are the following:
The name object must be UTF-8 encoded.
A device-specific color space (DeviceGray) without an appropriate output intent is used.
The font Helvetica must be embedded.
The document does not conform to the requested standard.
When signing, information about the digital signature (such as a stamp) is added to the document; it is for this reason that it no longer meets the standard.
Started by eliminating each of these errors, I have already eliminated the Helvetica font error with the following code:
String path = Sign.class.getResource("helvetica.ttf").toExternalForm();
FontFactory.register(path);
BaseFont base = BaseFont.createFont(path, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
Font font = new Font(base);
appearance.setLayer2Font(font);
However, I have not eliminated these two errors:
The name object must be UTF-8 encoded.
A device-specific color space (DeviceGray) without an appropriate output intent is used.
The document does not conform to the requested standard.
The error of the color space, I tried to eliminate it with the following code, but didn't help at all
because it keeps getting the error when I do the validation again.
path = Sign.class.getResource("sRGBColorSpaceProfile.icm").toExternalForm();
ICC_Profile icc = ICC_Profile.getInstance(new FileInputStream(path));
stamper.getWriter().setOutputIntents("Custom", "", "http://www.color.org", "sRGB IEC61966-2.1", icc);
I hope you can help me see how to eliminate the errors so that it complies with the PDF/A standard, or if there is another alternative that allows me to convert to PDF/A and then sign it with the Itext 5.5.5 library.
I appreciate the help you can give me.Thank you
UPDATE
This the code that I use to sign documents with the Itext 5.5.5 library.
String pkcs11Config = "name=NAME" + "\n" + "library=PATH";
ByteArrayInputStream configStream = new ByteArrayInputStream(pkcs11Config.getBytes());
Provider pkcs11Provider = new sun.security.pkcs11.SunPKCS11(configStream);
Security.addProvider(pkcs11Provider);
KeyStore ks = KeyStore.getInstance("PKCS11");
ks.load(null, password);
PdfReader reader = new PdfReader(pdfInput);
FileOutputStream os = new FileOutputStream(pdfOutputTemp);
PdfStamper stamper = PdfStamper.createSignature(reader, os, '\0', null, true);
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
String path = Sign.class.getResource("helvetica.ttf").toExternalForm();
FontFactory.register(path);
BaseFont base = BaseFont.createFont(path, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
Font font = new Font(base);
appearance.setLayer2Font(font);
path = Sign.class.getResource("sRGBColorSpaceProfile.icm").toExternalForm();
ICC_Profile icc = ICC_Profile.getInstance(new FileInputStream(path));
stamper.getWriter().setOutputIntents("Custom", "", "http://www.color.org", "sRGB IEC61966-2.1", icc);
...
PrivateKey key = (PrivateKey) ks.getKey(alias, pass);
ExternalSignature es = new PrivateKeySignature(key, DigestAlgorithms.SHA256, null);
ExternalDigest digest = new BouncyCastleDigest();
MakeSignature.signDetached(appearance, digest, es, chain, crlList, null, tsc, 0, CryptoStandard.CADES);
UPDATE 2
I tried changing the PdfStamper to PdfAStamper, but when I try to sign a document it returns the following error:
com.itextpdf.text.pdf.PdfAConformanceException: Annotation of type /Widget should have Contents key.
at com.itextpdf.text.pdf.internal.PdfA2Checker.checkAnnotation(PdfA2Checker.java:707)
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:3480)
at com.itextpdf.text.pdf.PdfWriter.checkPdfIsoConformance(PdfWriter.java:3476)
at com.itextpdf.text.pdf.PdfAnnotation.toPdf(PdfAnnotation.java:989)
at com.itextpdf.text.pdf.PdfWriter$PdfBody.addToObjStm(PdfWriter.java:292)
at com.itextpdf.text.pdf.PdfWriter$PdfBody.add(PdfWriter.java:382)
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:1395)
at com.itextpdf.text.pdf.PdfStamperImp.addAnnotation(PdfStamperImp.java:1408)
at com.itextpdf.text.pdf.PdfSignatureAppearance.preClose(PdfSignatureAppearance.java:1285)
at com.itextpdf.text.pdf.security.MakeSignature.signDetached(MakeSignature.java:243)
UPDATE 3
I updated the itext library from 5.5.5 to 5.5.13.1, and it worked better.
If I use the PdfStamper, I can sign the document but when I use the PDF/A validator, it returns the following errors:
Validating file "DOCUMENT.pdf" for conformance level pdfa-2a
A device-specific color space (DeviceGray) without an appropriate output intent is used.
The document does not conform to the requested standard.
The document contains device-specific color spaces.
The document does not conform to the PDF/A-2a standard.
Done.
The error that said: "The name object must be UTF-8 encoded." doesn't appear anymore.
Also, I tried to use the PdfStamper. When I try to sign a document it returns a different exception than before. This is the new exeption:
com.itextpdf.text.pdf.PdfAConformanceException: DeviceGray shall only be used if a device independent DefaultGray colour space has been set when the DeviceGray colour space is used, or if a PDF/A OutputIntent is present.
at com.itextpdf.text.pdf.internal.PdfA2Checker.close(PdfA2Checker.java:829)
at com.itextpdf.text.pdf.PdfAStamperImp.close(PdfAStamperImp.java:227)
at com.itextpdf.text.pdf.PdfSignatureAppearance.preClose(PdfSignatureAppearance.java:1348)
at com.itextpdf.text.pdf.security.MakeSignature.signDetached(MakeSignature.java:244)
Try this, open a digitally signed document in the Preview App in your Mac (you can use Adobe or any other software to sign) drag the signature and then save. Now open the file in Adobe Reader, the signature has been removed and converted to an AcroForm.
I already tried setting the CertificationLevel to not allow changes.
Code:
String outputPath = getOutputPath(this.files.getDocumentPath());
Certificate[] chain = {this.files.getCertificate()};
IExternalSignature pks = new PrivateKeySignature(this.files.getPrivateKey(), "SHA-512", "BC");
PdfReader reader = new PdfReader(this.files.getDocumentPath());
StampingProperties properties = new StampingProperties();
properties.useAppendMode();
ImageData imgData = ImageDataFactory.create(this.files.getSignaturePath(), true);
PdfSigner signer = new PdfSigner(reader, new FileOutputStream(outputPath), properties);
signer.setFieldName(signatureName);
PdfSignatureAppearance appearance = signer.getSignatureAppearance();
appearance.setPageNumber(page);
appearance.setPageRect(this.rectangle);
appearance.setSignatureGraphic(imgData);
appearance.setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC);
signer.setCertificationLevel(PdfSigner.CERTIFIED_NO_CHANGES_ALLOWED);
signer.signDetached(new BouncyCastleDigest(), pks, chain, null, null, null, 0, PdfSigner.CryptoStandard.CMS);
Docusign nailed this, cant seem to find a way to fix it.
I want to return the Document object from below code.
At present I get a document has no pages exception.
private static Document GeneratePdfAcroFields(PdfReader reader, Document docReturn)
{
if (File.Exists(System.Configuration.ConfigurationSettings.AppSettings["TEMP_PDF"]))
File.Delete(System.Configuration.ConfigurationSettings.AppSettings["TEMP_PDF"]);
PdfStamper stamper = new PdfStamper(reader, new FileStream(System.Configuration.ConfigurationSettings.AppSettings["TEMP_PDF"],FileMode.Create));
AcroFields form = stamper.AcroFields;
///INSERTING TEXT DYNAMICALLY JUST FOR EXAMPLE.
form.SetField("topmostSubform[0].Page16[0].topmostSubform_0_\\.Page78_0_\\.TextField3_9_[0]", "This value was dynamically added.");
stamper.FormFlattening = false;
stamper.Close();
FileStream fsRead = new FileStream(System.Configuration.ConfigurationSettings.AppSettings["TEMP_PDF"], FileMode.Open);
Document docret = new Document(reader.GetPageSizeWithRotation(1));
return docret;
}
Thanks Chris.
Just to reiterate what #BrunoLowagie is saying, passing Document objects around almost never makes >sense. Despite what the name might sound like, a Document doesn't represent a PDF in any way. Calling >ToString() or GetBytes() (if that method actually existed) wouldn't get you a PDF. A Document is just a >one-way funnel for passing human-friendly commands over to an engine that actually writes raw PDF >tokens. The engine, however, is also not even a PDF. The only thing that truly is a PDF is the raw >bytes of the stream that is being written to. – Chris Haas
After following the "Signing a document using a smart card and PKCS#11" topic in http://itextpdf.com/book/digitalsignatures and creating a code sample similar to the provided one, the signed file signature is invalid in Adobe Reader, the signature appearance has the name of the non-repudiation certificate (i.e., the name of the eID owner) but in Adobe Reader's Signature Panel shows:
The error occured while validating:
I'm using a Gemalto PinPad and the Portuguese eID pteidpkcs11.dll installed with the eID middleware software, located in C:\Windows\System32.
I've tried:
Null checking
Manually creating the Certificate chain, as the Certificate[] returned by ks.getCertificateChain("CITIZEN SIGNATURE CERTIFICATE"); only has the signature certificate
As an alternative you can sign with the portuguese eid card (Cartão de Cidadão) using only a java component available on www.poreid.org. It is also available on maven central repository with the artifactid poreid
Here is an example based on the sample provided in the itext documentation
public void createPdf(String filename) throws IOException, DocumentException {
Document document = new Document();
PdfWriter.getInstance(document, new FileOutputStream(filename));
document.open();
document.add(new Paragraph("Assinado com o Cartão de Cidadão!"));
document.close();
}
public void signPdf(String src, String dest)
throws IOException, DocumentException, GeneralSecurityException {
KeyStore ks = KeyStore.getInstance(POReIDConfig.POREID);
ks.load(null);
PrivateKey pk = (PrivateKey) ks.getKey(POReIDConfig.AUTENTICACAO, null);
Certificate[] chain = ks.getCertificateChain(POReIDConfig.AUTENTICACAO);
// reader and stamper
PdfReader reader = new PdfReader(src);
FileOutputStream os = new FileOutputStream(dest);
PdfStamper stamper = PdfStamper.createSignature(reader, os, '\0');
// appearance
PdfSignatureAppearance appearance = stamper .getSignatureAppearance();
appearance.setReason("qualquer motivo");
appearance.setLocation("qualquer localização");
appearance.setVisibleSignature(new Rectangle(72, 732, 144, 780), 1, "primeira assinatura");
// digital signature
ExternalSignature es = new PrivateKeySignature(pk, "SHA-256", POReIDConfig.POREID);
ExternalDigest digest = new ProviderDigest(null); // find provider
MakeSignature.signDetached(appearance, digest, es, chain, null, null, null, 0, CryptoStandard.CMS);
}
public static void main(String[] args) throws DocumentException, IOException, GeneralSecurityException {
Security.addProvider(new POReIDProvider());
App exemplo = new App();
exemplo.createPdf("/home/quim/exemplo.pdf");
exemplo.signPdf("/home/quim/exemplo.pdf","/home/quim/exemplo.assinado.pdf");
}
The provided code sample tries to get the PrivateKey of the signature certificate, I found it odd but figured it was just used as a reference. Navigating through the stack trace of the exception that is triggered when the user cancels the process in the PinPad gave me the following idea, which, fortunately, solved this:
Create a custom com.itextpdf.text.pdf.security.ExternalSignature implementation
Implement an utility class that, using the sun.security.pkcs11.wrapper.PKCS11 wrapper, interacts with your eID pkcs11 dll (in my case, pteidpkcs11.dll) and provides a signing method that receives a byte[] message which is then sent to the SmartCard reader to be signed, and returns the byte[] result of this operation
Use the utility class in your CustomExternalSignature.sign(...)
Some tips that you can use if you're developing for the Portuguese eID Cartão Cidadão:
For the second item of the previous list, I'm using the PTeID4JPKCS11 class from an opensource project named pteid4j created by André Barbosa, you just need to call PTeID4JPKCS11.getInstance().sign(...);
Regarding the Hash and Encryption algorithm required by the ExternalSignature interface, the hash is SHA-1 and the Encryption RSA
I've been working on digital signature for PDF documents using Portuguese citizen card, and here is what I have:
public void signCAdES(...) {
String pkcs11Config = "name=GemPC" + "\n" + "library=C:\\WINDOWS\\SysWOW64\\pteidpkcs11.dll";
ByteArrayInputStream configStream = new ByteArrayInputStream(pkcs11Config.getBytes());
Provider pkcs11Provider = new sun.security.pkcs11.SunPKCS11(configStream);
//provider_name: SunPKCS11-GemPC
Security.addProvider(pkcs11Provider);
javax.security.auth.callback.CallbackHandler cmdLineHdlr = new DialogCallbackHandler();
KeyStore.Builder builder = KeyStore.Builder.newInstance("PKCS11", pkcs11Provider,
new KeyStore.CallbackHandlerProtection(cmdLineHdlr));
KeyStore ks= builder.getKeyStore();
PdfReader reader = new PdfReader(src);
FileOutputStream os = new FileOutputStream(dest);
PdfStamper stamper = PdfStamper.createSignature(reader, os, '\0', new File(tempPath), true);
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
appearance.setReason(reason);
appearance.setLocation(location);
appearance.setCertificationLevel(level);
String alias = "CITIZEN SIGNATURE CERTIFICATE";
//certificates from electronic card and resources folder
Certificate[] certs = getSignatureCertificatesChain(ks);
PrivateKey pk = (PrivateKey) ks.getKey(alias, null);
ExternalSignature es = new PrivateKeySignature(pk, "SHA-1", pkcs11Provider.getName());
ExternalDigest digest = new BouncyCastleDigest();
MakeSignature.signDetached(appearance, digest, es, certs, null, null, null, 0, MakeSignature.CryptoStandard.CADES);
}
I have to build the certificates chain (getSignatureCertificatesChain(ks)) too since ks.getCertificateChain("CITIZEN SIGNATURE CERTIFICATE") only gives one certificate, and then the card itself doesn't have all the certificates so I have to get the missing ones at pki.cartaodecidadao.pt website and put them in a resources folder. Basically, I build my chain using both certificates in the card and in the resources folder, by linking them with the values in certificate.getIssuerX500Principal().getName() and certificate.getSubjecX500Principal().getName() (different cards can have different certificates of the same types, since the validity can vary so in one same type there can be 004 or 008 for example).
From what I understood, itext support for CAdES (MakeSignature.CryptoStandard.CADES) is more recent, but you need to use this since using MakeSignature.CryptoStandard.CMS might result in a signature that does not satisfy all the standards for CAdES (for example, missing the signing-certificate attribute - see http://docbox.etsi.org/ESI/Open/Latest_Drafts/prEN-319122-1v003-CAdES-core-STABLE-DRAFT.pdf).
The only minor issue with this code thought, is that there might be some optional attributes missing. I have used a validator from this tool https://github.com/arhs/sd-dss and while the signature generated passes the validation, it still gives a warning that the attribute issuer-serial is missing. I've created a post in hope anyone knows how to include the attribute here: CAdES Digital Signature
ks.getCertificateChain("CITIZEN SIGNATURE CERTIFICATE")
should return the certificate chain.
Where I can find a working online tool?
Suggestion: Make a call to the organization responsible for the middleware and ask for support.