The samples of itextsharp does not cover the situatuion when the certificate is on the smart card AND the signature need timestamp.
1. First sample: Sign without timestamp and with smart card
How to sign with a smartcard using an external signature dictionary with iTextSharp and .NET 2.0
This sample uses the SignedCms class of the .NET BCL. The SignedCms class creates the complete PKCS#7 message, and it supports when the certificate is on the smart card, but the signature doesn’t have a timestamp. This class is sealed, and no possibility to extend the data with timetamp information.
2. Second sample: Sign with timestamp and without smart card
How to sign with a timestamp and OCSP in C#
The second sample creates the PKCS#7 message with the Bouncy Castle Crypto API, which is packaged with the iTextSharp. The Bouncy Castle API calculates the signature HASH with the private key value of the X509 certificate, and therefore the smart card certificate is useless for this scenario.
The key in this problem is the SetExternalDigest method: if I can calculate the SHA1withRSA HASH with an external component, then the Bouncy Castle API can use the external value.
The RSACryptoServiceProvider class can create SHA1withRSA HASH, but it doesn’t support the smart card too. The reliable solution is the WINAPI, it supports the smart card.
The sample C# code below uses the CryptoAPI PInvoke helper class from Alejandro Campos Magencio.
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using iTextSharp.text.pdf;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Security;
namespace PdfSignerTest
{
class Program
{
static void Main(string[] args)
{
try
{
Console.WriteLine("Start");
string inputFileName = @"..\..\..\Report.pdf";
string outputFileName = @"..\..\..\Signed.pdf";
string timeStampUrl = "http://www.xxxx.com/timestamp.cgi";
using (FileStream inputPdfStream = new FileStream(inputFileName, FileMode.Open, FileAccess.Read))
{
using (FileStream outputPdfStream = new FileStream(outputFileName, FileMode.Create, FileAccess.Write))
{
X509Certificate2 card = SelectCertificate();
SignPdf(card, inputPdfStream, outputPdfStream, false, "Test Reason", "Test Location", "Test Contact", timeStampUrl, "teszt", "teszt");
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
finally
{
Console.WriteLine("Done.");
Console.ReadLine();
}
}
/// <summary>
///
/// </summary>
/// <param name="certificate">smart card certificate</param>
/// <param name="inputPdfStream">input PDF</param>
/// <param name="outputPdfStream">output PDF (signed)</param>
/// <param name="append">append if <CODE>true</CODE> the signature and all the other content will be added as a new revision thus not invalidating existing signature</param>
/// <param name="reason">Reason field of signature</param>
/// <param name="location">Location field of signature</param>
/// <param name="contact">Contact field of signature</param>
/// <param name="tsaClientUrl">Timestamp server URL</param>
/// <param name="tsaClientLogin">Login user for timestamp server (BASIC authentication)</param>
/// <param name="tsaClientPwd">Login password for timestamp server</param>
public static void SignPdf(X509Certificate2 certificate, Stream inputPdfStream, Stream outputPdfStream, bool append, string reason, string location, string contact, string tsaClientUrl, string tsaClientLogin, string tsaClientPwd)
{
// Building certificate chain for the OCSP verification
X509Chain ch = new X509Chain();
ch.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
ch.Build(certificate);
Org.BouncyCastle.X509.X509Certificate[] chain = new Org.BouncyCastle.X509.X509Certificate[ch.ChainElements.Count];
Org.BouncyCastle.X509.X509CertificateParser cp = new Org.BouncyCastle.X509.X509CertificateParser();
int i = 0;
foreach (X509ChainElement element in ch.ChainElements)
{
Org.BouncyCastle.X509.X509Certificate chainElement = cp.ReadCertificate(element.Certificate.RawData);
chain[i] = chainElement;
i++;
}
//Org.BouncyCastle.X509.X509CertificateParser cp = new Org.BouncyCastle.X509.X509CertificateParser();
//Org.BouncyCastle.X509.X509Certificate[] chain = new Org.BouncyCastle.X509.X509Certificate[] { cp.ReadCertificate(certificate.RawData) };
PdfReader reader = new PdfReader(inputPdfStream);
PdfStamper st = PdfStamper.CreateSignature(reader, outputPdfStream, '\0', null, append);
PdfSignatureAppearance sap = st.SignatureAppearance;
sap.SetCrypto(null, chain, null, PdfSignatureAppearance.SELF_SIGNED);
sap.Reason = reason;
sap.Contact = contact;
sap.Location = location;
sap.SetVisibleSignature(new iTextSharp.text.Rectangle(50, 50, 200, 100), 1, null);
PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, new PdfName("adbe.pkcs7.detached"));
dic.Reason = sap.Reason;
dic.Location = sap.Location;
dic.Contact = sap.Contact;
dic.Date = new PdfDate(sap.SignDate);
sap.CryptoDictionary = dic;
int contentEstimated = 15000;
Dictionary<PdfName, int> exc = new Dictionary<PdfName, int>();
exc[PdfName.CONTENTS] = contentEstimated * 2 + 2;
sap.PreClose(exc);
PdfPKCS7 sgn = new PdfPKCS7(null, chain, null, "SHA1", false);
IDigest messageDigest = DigestUtilities.GetDigest("SHA1");
Stream data = sap.RangeStream;
byte[] buf = new byte[8192];
int n;
while ((n = data.Read(buf, 0, buf.Length)) > 0)
{
messageDigest.BlockUpdate(buf, 0, n);
}
byte[] hash = new byte[messageDigest.GetDigestSize()];
messageDigest.DoFinal(hash, 0);
DateTime cal = DateTime.Now;
byte[] ocsp = null;
if (chain.Length >= 2)
{
String url = PdfPKCS7.GetOCSPURL(chain[0]);
if (url != null && url.Length > 0)
ocsp = new OcspClientBouncyCastle(chain[0], chain[1], url).GetEncoded();
//File.WriteAllBytes(@"..\..\..\ocsp.pdf", ocsp);
}
byte[] sh = sgn.GetAuthenticatedAttributeBytes(hash, cal, ocsp);
// SHA1withRSA calculated by CAPI
byte[] signedHashValue = SignSHA1withRSA(certificate, sh);
sgn.SetExternalDigest(signedHashValue, hash, "RSA");
byte[] paddedSig = new byte[contentEstimated];
if (!string.IsNullOrEmpty(tsaClientUrl))
{
TSAClientBouncyCastle tsc = new TSAClientBouncyCastle(tsaClientUrl, tsaClientLogin, tsaClientPwd);
byte[] encodedSigTsa = sgn.GetEncodedPKCS7(hash, cal, tsc, ocsp);
System.Array.Copy(encodedSigTsa, 0, paddedSig, 0, encodedSigTsa.Length);
if (contentEstimated + 2 < encodedSigTsa.Length)
throw new ApplicationException("Not enough space for signature");
}
else
{
byte[] encodedSig = sgn.GetEncodedPKCS7(hash, cal, null, ocsp);
System.Array.Copy(encodedSig, 0, paddedSig, 0, encodedSig.Length);
if (contentEstimated + 2 < encodedSig.Length)
throw new ApplicationException("Not enough space for signature");
}
PdfDictionary dic2 = new PdfDictionary();
dic2.Put(PdfName.CONTENTS, new PdfString(paddedSig).SetHexWriting(true));
sap.Close(dic2);
}
public static byte[] SignSHA1withRSA(X509Certificate2 certificate, byte[] input)
{
const Int32 CRYPT_ACQUIRE_USE_PROV_INFO_FLAG = 0x00000002;
const Int32 CRYPT_ACQUIRE_COMPARE_KEY_FLAG = 0x00000004;
IntPtr privateKeyHandle = IntPtr.Zero;
bool isCallerNeedFreeKeyHandle = false;
IntPtr hashHandle = IntPtr.Zero;
byte[] result = null;
try
{
IntPtr cardHandle = certificate.Handle;
Int32 pdwKeySpec = Crypto.AT_SIGNATURE;
if (!Crypto.CryptAcquireCertificatePrivateKey(cardHandle, CRYPT_ACQUIRE_USE_PROV_INFO_FLAG |
CRYPT_ACQUIRE_COMPARE_KEY_FLAG, IntPtr.Zero, ref privateKeyHandle, ref pdwKeySpec, ref isCallerNeedFreeKeyHandle))
{
throw new CryptographicException(Marshal.GetLastWin32Error());
}
if (!Crypto.CryptCreateHash(privateKeyHandle, Crypto.CALG_SHA1, IntPtr.Zero, 0, ref hashHandle))
{
throw new CryptographicException(Marshal.GetLastWin32Error());
}
MemoryStream streamInput = new MemoryStream(input);
byte[] buffer = new byte[4096];
while (true)
{
int read = streamInput.Read(buffer, 0, buffer.Length);
if (read == 0)
break;
if (!Crypto.CryptHashData(hashHandle, buffer, read, 0))
{
throw new CryptographicException(Marshal.GetLastWin32Error());
}
}
int pwdSigLen = 0;
if (!Crypto.CryptSignHash(hashHandle, pdwKeySpec, null, 0, null, ref pwdSigLen))
{
throw new CryptographicException(Marshal.GetLastWin32Error());
}
result = new byte[pwdSigLen];
if (!Crypto.CryptSignHash(hashHandle, pdwKeySpec, null, 0, result, ref pwdSigLen))
{
throw new CryptographicException(Marshal.GetLastWin32Error());
}
// CAPI generated hash has a different indian order
Array.Reverse(result);
}
finally
{
if (hashHandle != IntPtr.Zero)
{
if (!Crypto.CryptDestroyHash(hashHandle))
{
throw new CryptographicException(Marshal.GetLastWin32Error());
}
}
if (isCallerNeedFreeKeyHandle && privateKeyHandle != IntPtr.Zero)
{
if (!Crypto.CryptReleaseContext(privateKeyHandle, 0))
{
throw new CryptographicException(Marshal.GetLastWin32Error());
}
}
}
return result;
}
public static X509Certificate2 SelectCertificate()
{
X509Store st = new X509Store(StoreName.My, StoreLocation.CurrentUser);
st.Open(OpenFlags.ReadOnly);
X509Certificate2Collection col = st.Certificates.Find(X509FindType.FindByKeyUsage, X509KeyUsageFlags.NonRepudiation, true);
X509Certificate2 card = null;
X509Certificate2Collection sel = X509Certificate2UI.SelectFromCollection(col, "Certificates", "Select one to sign", X509SelectionFlag.SingleSelection);
if (sel.Count > 0)
{
X509Certificate2Enumerator en = sel.GetEnumerator();
en.MoveNext();
card = en.Current;
}
st.Close();
return card;
}
}
}