How to prevent MacOS PreviewAPP from removing PKCS7 Signature - pdf

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.

Related

IText7 PDF SignDeferred Mystery - Signed and All Signatures are Valid but with Unsigned Changes

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.

"ERROR unspecified algorithm" when signing a PDF with itextsharp 5.5.13.2 and SHA-256

Good morning, I am trying to sign a PDF document using ItextSharp 5.5.13.2 and SHA-256 for the signature process but at the moment of signing I get the error "Specified Algorithm is Invalid". This error does not happen when I use SHA-1 with .NetFramework 4.7.2 to sign the PDF, the next method is the one I use to sign the PDF.
public void SignPDF(string PathSourceDoc, string PathTargetDoc, X509Certificate2 certificate, string pathLogo)
{
using (PdfReader reader = new PdfReader(PathSourceDoc))
using (var writer = new FileStream(PathTargetDoc, FileMode.Create, FileAccess.Write))
using (var stamper = PdfStamper.CreateSignature(reader, writer, '\0', null, true))
{
var signature = stamper.SignatureAppearance;
signature.CertificationLevel = PdfSignatureAppearance.NOT_CERTIFIED;
signature.Reason = "My Reason";
signature.Location = "My Location";
signature.SignDate = DateTime.Now;
signature.Acro6Layers = true;
PdfSignature objSignature = new PdfSignature(PdfName.ADOBE_PPKMS, PdfName.ADBE_PKCS7_SHA1);
objSignature.Date = new PdfDate(signature.SignDate);
signature.CryptoDictionary = objSignature;
var bcCert = DotNetUtilities.FromX509Certificate(certificate);
string name = CertificateInfo.GetSubjectFields(bcCert).GetField("CN");
string industry = CertificateInfo.GetSubjectFields(bcCert).GetField("O");
string position = CertificateInfo.GetSubjectFields(bcCert).GetField("T");
DateTime date = DateTime.Now;
signature.Layer2Text = "Digital Signed by: " + name + "\n" +
"Reason: " + "My Reason" + "\n" +
"Date: " + date;
signature.Layer2Font = new Font(iTextSharp.text.Font.FontFamily.TIMES_ROMAN, 8);
Image img = Image.GetInstance(pathLogo);
signature.SignatureGraphic = img;
signature.SignatureRenderingMode = PdfSignatureAppearance.RenderingMode.GRAPHIC_AND_DESCRIPTION;
Rectangle rect = new Rectangle(50, 50, 300, 110);
signature.SetVisibleSignature(rect, 1, null);
var standard = CryptoStandard.CADES;
/*I tried this way but I get an error of type Internal.Cryptography.CryptoThrowHelper.WindowsCryptographicException: 'The requested operation is not supported.'*/
X509Certificate cert = certificate;
X509Certificate2 signatureCert = new X509Certificate2(cert);
var pk = Org.BouncyCastle.Security.DotNetUtilities.GetKeyPair(signatureCert.PrivateKey).Private;// the error is generated in this line (Internal.Cryptography.CryptoThrowHelper.WindowsCryptographicException: 'The requested operation is not supported.').
IExternalSignature es = new PrivateKeySignature(pk, "SHA-256");
/****************************************************************************************************************************************************************/
X509Certificate2Signature externalSignature = new X509Certificate2Signature(certificate, DigestAlgorithms.SHA1); /*throws an exception on this line of type "System.ArgumentException: 'Unknown encryption algorithm System.Security.Cryptography.RSACng'"
* when using .NET Core 3.1. this always happens, it doesn't matter if I use sha1 or use any other algorithm, it always generates error*/
MakeSignature.SignDetached(signature, externalSignature, new[] { bcCert }, null, null, null, 0, standard);
}
}
The method receives as input parameters the path of the PDF that I need to sign, the path of the pdf that will be created when signing, the certificate and the path of the logo for displaying the image in the signature. Actually I don't know what I'm doing wrong since I've been researching related questions and it should work with SHA-1 and SHA-256 and .NetFramework.
Then I migrated the project to .NetCore 3.1 in order to try to fix the problem but instead I got a new error (It is commented in the code). My goal is to use .NetCore and to allow me to sign a pdf using sha256. I have no problem modifying the SignPdf method in order to make the program work.
Any contribution or information link is appreciated. Thanks for your help.
Psdt: This is the stacktrace is as follows...
this is the stacktrace image of the error when the project was migrated to .Net Core 3.1
Apparently the Microsoft Crypto APIs used by X509Certificate2Signature do not support SHA-256.
You use a X509Certificate2Signature instance as IExternalSignature argument of MakeSignature.SignDetached.
If the PrivateKey of your X509Certificate2 is a RSACryptoServiceProvider (which appears to be the case for your certificate), X509Certificate2Signature.Sign calls
RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)certificate.PrivateKey;
return rsa.SignData(message, hashAlgorithm);
RSACryptoServiceProvider.SignData at first apparently does support "SHA256" because before forwarding execution to SignHash it must have calculated the hash value to sign.
RSACryptoServiceProvider.SignHash, though, does not support SHA-256, according to the Microsoft docs on that method:
Remarks
This method creates a digital signature that is verified using the VerifyHash method.
The valid hash algorithms are SHA1 and MD5. The algorithm identifier can be derived from the hash name by using the MapNameToOID method.
Due to collision problems with SHA1 and MD5, Microsoft recommends a security model based on SHA256 or better.
(Viewed 2021-06-24, 11:07)
Thus, you get the "Specified Algorithm is Invalid" error.
Essentially, therefore, one should not base one's IExternalSignature implementation for RSA signing on RSACryptoServiceProvider anymore.
Maybe this is why X509Certificate2Signature was not ported to iText version 7...
An alternative approach might be to check whether the PrivateKey is a RSACng first and in that case use it as a RSACng. This Cryptography Next Generation (CNG) implementation of the RSA algorithm should support better hashing algorithms...

Certify XFA document using iTextSharp

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?

PDF Digital Signature in java and signature verification in c# (iText)

I'm developing a webserver in c# that performs digital signatures validations, to ensure that the pdf files weren't modified.
I'm using iText and iTextSharp for this.
But the client-side is based on a java applet. I perform the digital signatures in that java applet. In java i'm able to make the signatures and then verify them. But if I verify the signature in C# it is given a nullreferenceexception.
Here is my Java digital signature code:
String path = "C:/Users/a/Desktop/cert.pfx";
String keystore_password = "fgf";
String key_password = "fgf";
////
BouncyCastleProvider provider = new BouncyCastleProvider();
Security.addProvider(provider);
KeyStore ks = KeyStore.getInstance("pkcs12", "BC");
ks.load(new FileInputStream(path), keystore_password.toCharArray());
String alias = (String)ks.aliases().nextElement();
PrivateKey pk = (PrivateKey) ks.getKey(alias, key_password.toCharArray());
Certificate[] chain = ks.getCertificateChain(alias);
PdfReader reader = new PdfReader(src);
dest = "C:/Users/a/Desktop/" + dest;
FileOutputStream os = new FileOutputStream(dest);
PdfStamper stamper = PdfStamper.createSignature(reader, os, '\0');
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
ExternalSignature es = new PrivateKeySignature(pk, "SHA-256", "BC");
ExternalDigest digest = new BouncyCastleDigest();
MakeSignature.signDetached(appearance, digest, es, chain, null, null, null, 0, CryptoStandard.CMS);
And my C# verification code:
PdfReader reader = new PdfReader(pdfFile);
AcroFields af = reader.AcroFields;
var names = af.GetSignatureNames();
if (names.Count == 0)
{
throw new InvalidOperationException("No Signature present in pdf file.");
}
foreach (string name in names)
{
if (!af.SignatureCoversWholeDocument(name))
{
throw new InvalidOperationException(string.Format("The signature: {0} does not covers the whole document.", name));
}
PdfPKCS7 pk = af.VerifySignature(name);
var cal = pk.SignDate;
var pkc = pk.Certificates;
if (!pk.Verify())
{
Console.WriteLine("The signature is not valid.");
return false;
}
}
In the line af.VerifySignature(name); the NullReferenceException is thrown up!
The fun thing is, if I perform the signatures with C# code I'm able to verify it in java, since I add these instructions:
BouncyCastleProvider provider = new BouncyCastleProvider();
Security.addProvider(provider);
I think my problem relies on some byte conversions... But in C# I don't know how to call a bouncycastleprovider.
Can you help me?
My best regards: William.
The stacktrace posted by the OP in a comment
...
at org.bouncycastle.security.SignerUtil.getSigner(String algorithm)
at iTextSharp.text.pdf.PdfPKCS7..ctor(Byte[] contentsKey)
at iTextSharp.text.pdf.AcroFields.VerifySignature(String name)
at SignatureLibrary.iText.PDFValidation(String pdfFile)
in ...\\SignatureLibrary\\SignatureLibrary\\iText.cs:line 122
contains the line iTextSharp.text.pdf.PdfPKCS7..ctor(Byte[] contentsKey) which indicates that the OP was not using the current iTextSharp version 5.5.0 but instead a version from before 5.3.0 (published June 2012): in version 5.3.0 PdfPKCS7 had been refactored into the namespace iTextSharp.text.pdf.security.
This refactoring was part of a major update of the whole iText signature creation and verification code, an update which introduced many new features.
Thus, the OP was advised to update the iTextSharp assembly, and indeed:
I downloaded the latest itextsharp version and it worked well the verification.

Signing a PDF with an eID using PKCS#11 and iText

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.