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

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.

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...

How to prevent MacOS PreviewAPP from removing PKCS7 Signature

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.

itextsharp signing pdf with signed hash

I'm trying to sign a pdf through a signing service. This service requires to send a hex encoded SHA256 digest and in return I receive a hex encoded signatureValue. Besides that I also receive a signing certificate, intermediate certificate, OCSP response, and TimeStampToken. However, I already get stuck trying to sign the pdf with the signatureValue.
I have read Bruno's white paper, browsed the internet excessively, and tried many different ways, but the signature keeps coming up as invalid.
My latest attempt:
First, prepare pdf
PdfReader reader = new PdfReader(src);
FileStream os = new FileStream(dest, FileMode.Create);
PdfStamper stamper = PdfStamper.CreateSignature(reader, os, '\0');
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
appearance.Certificate = signingCertificate;
IExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
MakeSignature.SignExternalContainer(appearance, external, 8192);
string hashAlgorithm = "SHA-256";
PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, false);
PdfSignatureAppearance appearance2 = stamper.SignatureAppearance;
Stream stream = appearance2.GetRangeStream();
byte[] hash = DigestAlgorithms.Digest(stream, hashAlgorithm);
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);
Hash byte[] sh and convert to string as follows
private static String sha256_hash(Byte[] value)
{
using (SHA256 hash = SHA256.Create())
{
return String.Concat(hash.ComputeHash(value).Select(item => item.ToString("x2"))).ToUpper();
}
}
and send to signing service. The received hex encoded signatureValue I then convert to bytes
private static byte[] StringToByteArray(string hex)
{
return Enumerable.Range(0, hex.Length).Where(x => x % 2 == 0).Select(x => Convert.ToByte(hex.Substring(x, 2), 16)).ToArray();
}
Finally, create signature
private void CreateSignature(string src, string dest, byte[] sig)
{
PdfReader reader = new PdfReader(src); // src is now prepared pdf
FileStream os = new FileStream(dest, FileMode.Create);
IExternalSignatureContainer external = new MyExternalSignatureContainer(sig);
MakeSignature.SignDeferred(reader, "Signature1", os, external);
reader.Close();
os.Close();
}
private class MyExternalSignatureContainer : IExternalSignatureContainer
{
protected byte[] sig;
public MyExternalSignatureContainer(byte[] sig)
{
this.sig = sig;
}
public byte[] Sign(Stream s)
{
return sig;
}
public void ModifySigningDictionary(PdfDictionary signDic) { }
}
What am I doing wrong? Help is very much appreciated. Thanks!
Edit: Current state
Thanks to help from mkl and following Bruno's deferred signing example I've gotten past the invalid signature message. Apparently I don't receive a full chain from the signing service, but just an intermediate certificate, which caused the invalid message. Unfortunately, the signature still has flaws.
I build the chain like this:
List<X509Certificate> certificateChain = new List<X509Certificate>
{
signingCertificate,
intermediateCertificate
};
In the sign method of MyExternalSignatureContainer I now construct and return the signature container:
public byte[] Sign(Stream s)
{
string hashAlgorithm = "SHA-256";
PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, false);
byte[] ocspResponse = Convert.FromBase64String("Base64 encoded DER representation of the OCSP response received from signing service");
byte[] hash = DigestAlgorithms.Digest(s, hashAlgorithm);
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, ocspResponse, null, CryptoStandard.CMS);
string messageDigest = Sha256_hash(sh);
// messageDigest sent to signing service
byte[] signatureAsByte = StringToByteArray("Hex encoded SignatureValue received from signing service");
sgn.SetExternalDigest(signatureAsByte, null, "RSA");
ITSAClient tsaClient = new MyITSAClient();
return sgn.GetEncodedPKCS7(hash, tsaClient, ocspResponse, null, CryptoStandard.CMS);
}
public class MyITSAClient : ITSAClient
{
public int GetTokenSizeEstimate()
{
return 0;
}
public IDigest GetMessageDigest()
{
return new Sha256Digest();
}
public byte[] GetTimeStampToken(byte[] imprint)
{
string hashedImprint = HexEncode(imprint);
// Hex encoded Imprint sent to signing service
return Convert.FromBase64String("Base64 encoded DER representation of TimeStampToken received from signing service");
}
}
Still get these messages:
"The signer's identity is unknown because it has not been included in the list of trusted identities and none or its parent
certificates are trusted identities"
"The signature is timestamped, but the timestamp could not be verified"
Further help is very much appreciated again!
"What am I doing wrong?"
The problem is that on one hand you start constructing a CMS signature container using a PdfPKCS7 instance
PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, false);
and for the calculated document digest hash retrieve the signed attributes to be
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, null, null, CryptoStandard.CMS);
to send them for signing.
So far so good.
But then you ignore the CMS container you started constructing but instead inject the naked signature bytes you got from your service into the PDF.
This cannot work as your signature bytes don't sign the document directly but instead they sign these signed attributes (and, therefore, indirectly the document as the document hash is one of the signed attributes). Thus, by ignoring the CMS container under construction you dropped the actually signed data...
Furthermore, the subfilter ADBE_PKCS7_DETACHED you use promises that the embedded signature is a full CMS signature container, not a few naked signature bytes, so the format also is wrong.
How to do it instead?
Instead of injecting the naked signature bytes you got from your service into the PDF as is, you have to set them as external digest in the PdfPKCS7 instance in which you originally started constructing the signature container:
sgn.SetExternalDigest(sig, null, ENCRYPTION_ALGO);
(ENCRYPTION_ALGO must be the encryption part of the signature algorithm, I assume in your case "RSA".)
and then you can retrieve the generated CMS signature container:
byte[] encodedSig = sgn.GetEncodedPKCS7(hash, null, null, null, CryptoStandard.CMS);
Now this is the signature container to inject into the document using MyExternalSignatureContainer:
IExternalSignatureContainer external = new MyExternalSignatureContainer(encodedSig);
MakeSignature.SignDeferred(reader, "Signature1", os, external);
Remaining issues
Having corrected your code Adobe Reader still warns about your signatures:
"The signer's identity is unknown because it has not been included in the list of trusted identities and none or its parent certificates are trusted identities"
This warning is to be expected and correct!
The signer's identity is unknown because your signature service uses merely a demo certificate, not a certificate for production use:
As you see the certificate is issued by "GlobalSign Non-Public HVCA Demo", and non-public demo issuers for obvious reasons must not be trusted (unless you manually add them to your trust store for testing purposes).
"The signature is timestamped, but the timestamp could not be verified"
There are two reasons why Adobe does not approve of your timestamp:
On one hand, just like above, the timestamp certificate is a non-public, demo certificate ("DSS Non-Public Demo TSA Responder"). Thus, there is no reason for the verifier to trust your timestamp.
On the other hand, though, there is an actual error in your timestamp'ing code, you apply the hashing algorithm twice! In your MyITSAClient class you have
public byte[] GetTimeStampToken(byte[] imprint)
{
string hashedImprint = Sha256_hash(imprint);
// hashedImprint sent to signing service
return Convert.FromBase64String("Base64 encoded DER representation of TimeStampToken received from signing service");
}
The imprint parameter of your GetTimeStampToken implementation is already hashed, so you have to hex encode these bytes and send them for timestamp'ing. But you apply your method Sha256_hash which first hashes and then hex encodes this new hash.
Thus, instead of applying Sha256_hash merely hex encode the imprint!

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.