Problem signing PDF from smart card - BouncyCastle, IAIK Wrapper, iText
Hello!
I need to sign and timestamp a PDF document with a smartcard. I'm using Java 1.6, iText to manage PDF, BouncyCastle to deal with cryptography and the free IAIK WRAPPER to access the smartcard.
I've already searched the Internet to solve my problem, read the PDF specifications about the signature and followed snippets that should've worked, but after a couple of weeks I still don't have working code, not even for the signature. All the tries I made yield messages like "Signature has been corrupted" or "Invalid signature" (I can't remember the exact messages, but they're not in English anyway :D ) when I verify the signature in Adobe Reader.
My first goal was to use an encapsulated signature, using filter Adobe.PPKLITE, subfilter adbe.pkcs7.sha1 and a DER-Encoded PKCS#7 object as content.
Among the tries I made, I used code such as (I don't include all modifications, just the ones I deem closer to the right approach):
...
// COMMON - START
///// selectedKey is a iaik.pkcs.pkcs11.objects.Key instance of the private key I'm taking from the SC
RSAPrivateKey signerPrivKey=(RSAPrivateKey)selectedKey;
CertificateFactory certificateFactory=CertificateFactory.getInstance("X.509");
///// correspondingCertificate is a iaik.pkcs.pkcs11.objects.X509PublicKeyCertificate instance of the certificate I'm taking from the SC
byte[] derEncodedCertificate=correspondingCertificate.getValue().getByteArrayValue();
X509Certificate signerCert=(X509Certificate)certificateFactory.generateCertificate(new ByteArrayInputStream(derEncodedCertificate));
Provider provider=new BouncyCastleProvider();
Security.addProvider(provider);
///// session is an instance of iaik.pkcs.pkcs11.Session
session.signInit(Mechanism.SHA1_RSA_PKCS, signerPrivKey);
File theFile = new File("C:\\toSign.pdf");
FileInputStream fis = new FileInputStream(theFile);
byte[] contentData = new byte[(int) theFile.length()];
fis.read(contentData);
fis.close();
PdfReader reader = new PdfReader(contentData);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PdfStamper stp = PdfStamper.createSignature(reader, baos, '\0');
PdfSignatureAppearance sap = stp.getSignatureAppearance();
// COMMON - END
java.security.cert.X509Certificate[] certs=new java.security.cert.X509Certificate[1];
CertificateFactory factory=CertificateFactory.getInstance("X.509");
certs[0]=(X509Certificate)factory.generateCertificate(new ByteArrayInputStream(correspondingCertificate.getValue().getByteArrayValue()));
sap.setSignDate(new GregorianCalendar());
sap.setCrypto(null, certs, null, null);
sap.setReason("This is the reason");
sap.setLocation("This is the Location");
sap.setContact("This is the Contact");
sap.setAcro6Layers(true);
PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_SHA1);
dic.setDate(new PdfDate(sap.getSignDate()));
dic.setName(PdfPKCS7.getSubjectFields((X509Certificate)certs[0]).getField("CN"));
sap.setCryptoDictionary(dic);
int csize = 4000;
HashMap exc = new HashMap();
exc.put(PdfName.CONTENTS, new Integer(csize * 2 + 2));
sap.preClose(exc);
MessageDigest md = MessageDigest.getInstance("SHA1");
InputStream s = sap.getRangeStream();
int read = 0;
byte[] buff = new byte[8192];
while ((read = s.read(buff, 0, 8192)) > 0)
md.update(buff, 0, read);
byte[] signature=session.sign(buff);
CMSSignedDataGenerator generator = new CMSSignedDataGenerator();
ArrayList list = new ArrayList();
for (int i = 0; i < certs.length; i++)
list.add(certs);
CertStore chainStore = CertStore.getInstance("Collection", new CollectionCertStoreParameters(list), provider);
generator.addCertificatesAndCRLs(chainStore);
CMSProcessable content = new CMSProcessableByteArray(md.digest());
CMSSignedData signedData = generator.generate(CMSSignedDataGenerator.ENCRYPTION_RSA, content, true, provider);
byte[] pk = signedData.getEncoded();
byte[] outc = new byte[csize];
PdfDictionary dic2 = new PdfDictionary();
System.arraycopy(pk, 0, outc, 0, pk.length);
dic2.put(PdfName.CONTENTS, new PdfString(outc).setHexWriting(true));
sap.close(dic2);
File newOne = new File("C:\\signed.pdf");
FileOutputStream fos = new FileOutputStream(newOne);
fos.write(baos.toByteArray());
fos.close();
...
I figured this is the right approach, but I need a way to generate the CMSSignedData instance, which can't be done using addSigner (the only documented way I found), since the private key is not extractable from a smart card...
Then I decided to give up and try with a detached signature:
...
// COMMON - START
// Same as above
// COMMON - END
sap.setSignDate(new GregorianCalendar());
java.security.cert.X509Certificate[] certs=new java.security.cert.X509Certificate[1];
CertificateFactory factory=CertificateFactory.getInstance("X.509");
certs[0]=(X509Certificate)factory.generateCertificate(new ByteArrayInputStream(correspondingCertificate.getValue().getByteArrayValue()));
sap.setCrypto(null, certs, null, PdfSignatureAppearance.SELF_SIGNED);
sap.setSignDate(java.util.Calendar.getInstance());
sap.setExternalDigest (new byte[8192], new byte[20], "RSA");
sap.preClose();
MessageDigest messageDigest = MessageDigest.getInstance ("SHA1");
byte buff[] = new byte[8192];
int n;
InputStream inp = sap.getRangeStream ();
while ((n = inp.read (buff)) > 0)
messageDigest.update (buff, 0, n);
byte hash[] = messageDigest.digest();
byte[] signature=session.sign(hash);
PdfSigGenericPKCS sg = sap.getSigStandard ();
PdfLiteral slit = (PdfLiteral)sg.get (PdfName.CONTENTS);
byte[] outc = new byte[(slit.getPosLength () - 2) / 2];
PdfPKCS7 sig = sg.getSigner ();
sig.setExternalDigest (session.sign(hash), hash, "RSA");
PdfDictionary dic = new PdfDictionary ();
byte[] ssig = sig.getEncodedPKCS7();
System.arraycopy (ssig, 0, outc, 0, ssig.length);
dic.put (PdfName.CONTENTS, new PdfString (outc).setHexWriting(true));
sap.close (dic);
File newOne = new File("C:\\signed.pdf");
FileOutputStream fos = new FileOutputStream(newOne);
fos.write(baos.toByteArray());
fos.close();
...
I'm still stuck to the signature process, can anyone please tell me what I'm doing wrong and help me (snippets would be deeply appreciated), maybe even changing approach in order to be able to add a digital timestamp?
Thank you very much in advance!
PS: I had also tried to use the SunPKCS11 provider to access the smart card, I gave up for similar problems, but if someone has suggestions using it, they're welcome! :D