- Home
- Forum
- ASP.NET Core - EJ 2
- Prepare PDF hash to be signed and sign externally with smart card not on the same system, then append the signed hash to the PDF document
Prepare PDF hash to be signed and sign externally with smart card not on the same system, then append the signed hash to the PDF document
Signing PDF documents with certificates present on the same system as the app works great. You can sign the document using a certificate file with a password or you can sign it using a smart card (non-exportable private key), where you are prompted to enter the PIN.
The problem arises when the signing process is separated between the back-end and front-end. Each user on the front-end should be able to independently sign PDF documents with their smart cards connected to their systems. The front-end is Angular + Electron, as such we are able to sign data using smart cards with node-webcrypto-p11.
Desired process:
1) Client requests hash of PDF to be signed from the back-end (.NET Core)
2) Hash should be constructed as such to include the empty signature field and everything needed to produce a valid signature - presumably not the whole PDF bytes, but the proper CMS structure
3) Returned hash is signed by Client using a smart card & PIN - resulting in a signed hash
4) Client posts the signed hash to the back-end
5) Signed hash is appended to the PDF document in the appropriate structure - resulting in a valid signature
The example provided by Dili Babu at How to add signed hash to pdf as signature is great but the resulting PDF contains an invalid signature. I think we're missing an essential part of the process.
If the process described above is possible, would you be able to provide an example?
Thank you,
Marko
|
//Load PDF document
PdfLoadedDocument loadedDocument = new PdfLoadedDocument(docStream);
//Get the existing PDF page.
PdfLoadedPage page = loadedDocument.Pages[0] as PdfLoadedPage;
Syncfusion.Pdf.Security.PdfSignature signature = newSyncfusion.Pdf.Security.PdfSignature(loadedDocument, page, null, "Sig1");
signature.Settings.CryptographicStandard = CryptographicStandard.CADES;
signature.Settings.DigestAlgorithm = DigestAlgorithm.SHA1;
//Create external signer
IPdfExternalSigner externalSignature = new ExternalSigner("SHA1");
//Please use your installed certificate thumprint
X509Store store = new X509Store("MY", StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
X509Certificate2Collection collection = (X509Certificate2Collection)store.Certificates;
//Find the certificate using thumb print.
X509Certificate2Collection fcollection = (X509Certificate2Collection)collection.Find(X509FindType.FindByThumbprint,"7402F6CD89410AA76816A1F0D82AC41923733B5B", false);
X509Certificate2 digitalID = fcollection[0];
List<X509Certificate2> certificates = new List<X509Certificate2>();
certificates.Add(digitalID);
signature.AddExternalSigner(externalSignature, certificates, null);
signature.EnableLtv = true;
//Save and close the document
MemoryStream stream = new MemoryStream();
loadedDocument.Save(stream);
loadedDocument.Close(true); |
|
//Please use your side certification thumbprint and private key if condition
public byte[] Sign(byte[] message, out byte[] timeStampResponse)
{
X509Store store = new X509Store("MY", StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
X509Certificate2Collection collection = (X509Certificate2Collection)store.Certificates;
//Find the certificate using thumb print.
X509Certificate2Collection fcollection = (X509Certificate2Collection)collection.Find(X509FindType.FindByThumbprint,"7402F6CD89410AA76816A1F0D82AC41923733B5B", false);
X509Certificate2 digitalID = fcollection[0];
byte[] signedBytes = null;
//This condition is based on the provided certificate private key
if (digitalID.PrivateKey is RSACryptoServiceProvider)
{
RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)digitalID.PrivateKey;
signedBytes = rsa.SignData(message, HashAlgorithm);
}
timeStampResponse = null;
return signedBytes;
}
|
|
Note: Please modify the above sample based on the installed certificate and private key |
Hello,
Thank you for your fast response and the provided example, which I have implemented and tested.
While it does give us the possibility of external signing with IPdfExternalSigner, where we can implement our own signing process, there is one essential part missing here, which is the preparation of the document before signing at a different time – not in the same session.
I would like to describe the process in a bit more detail.
It starts with the client requesting document bytes for signing from the server and also sends the certificate value, which is then used to create the X509Certificate2.
We get the bytes for signing by triggering the signing process (saving the document) as follows:
Syncfusion.Pdf.Security.PdfSignature signature = new Syncfusion.Pdf.Security.PdfSignature(loadedDocument, page, null, "Sig1");
signature.Settings.CryptographicStandard = CryptographicStandard.CMS;
signature.Settings.DigestAlgorithm = DigestAlgorithm.SHA256;
IPdfExternalSigner externalSignature = new ExternalSigner("SHA256");
List
X509Certificate2 certificate = new X509Certificate2(Convert.FromBase64String(certificateValue));
certificates.Add(certificate);
signature.AddExternalSigner(externalSignature, certificates, null);
In the Sign method implementation, we save the message (bytes for signing) to a .txt file for later use.
public byte[] Sign(byte[] message, out byte[] timeStampResponse)
{
timeStampResponse = null;
System.IO.File.WriteAllText("sig.txt", Convert.ToBase64String(message));
return message;
}
We then return the bytes to sign to the user as follows:
return Ok(System.IO.File.ReadAllText("sig.txt"));
- Here it is obvious, that we are missing a way to extract the bytes for signing from the prepared signature/document. This would simplify the above process, so it’s a method call instead of saving the bytes to a file, then reading from it (or saving to cache, etc.).
Client receives the bytes for signing and signs them using a smart card. We could also sign without a smart card, by certificates in user’s store, but that is not essential as the ending result is unfortunately the same.
Client then sends the signed message to the server.
This time we use a different implementation of IPdfExternalSigner’s Sign method as follows:
IPdfExternalSigner externalSignature = new FinalExternalSigner("SHA256", Convert.FromBase64String(signatureValue));
class FinalExternalSigner : IPdfExternalSigner
{
private string _hashAlgorithm;
public string HashAlgorithm => _hashAlgorithm;
public byte[] _signedData;
public FinalExternalSigner(string hashAlgorithm, byte[] signedData)
{
_hashAlgorithm = hashAlgorithm;
_signedData = signedData;
}
public byte[] Sign(byte[] message, out byte[] timeStampResponse)
{
timeStampResponse = null;
return _signedData;
}
}
We save the document as in the example:
MemoryStream stream = new MemoryStream();
loadedDocument.Save(stream);
loadedDocument.Close(true);
FileStream outStream = System.IO.File.OpenWrite("Output.pdf");
stream.WriteTo(outStream);
outStream.Flush();
stream.Dispose();
The resulting PDF (Output.pdf) properly contains the invisible signature with the certificate data, but the signature is invalid, and it says, “Document has been altered or corrupted since it was signed”.
It’s clear that we have a few missing pieces in this process.
Is there anything that we can implement differently to support this process?
Regards,
Marko
- 6 Replies
- 3 Participants
- Marked answer
-
MB Marko Bezjak
- Nov 30, 2020 04:16 AM UTC
- Mar 31, 2021 11:52 AM UTC