The idea of the web app
is to sign documents with a digital signature
that is loaded from a smart card
.
It is published and set to work on a local user
machine. I am using IIS
for that matter to set the bindings
and enable to accept client certificates
.
It communicates with a web app
that is hosted on the cloud
.
I am successfully getting the certificate
from the smart card
and the private key
as well.
I use the private key
to sign the document.
private InvoiceResult SignDocument(XmlDocument doc)
{
InvoiceResult resultValue;
try
{
var (resultValue2, certificate) = GetDefaultCertificateStoredOnTheCard();
resultValue = resultValue2;
SignXmlDocumentWithCertificate(doc, certificate);
resultValue = InvoiceResult.Success;
}
catch (Exception ex)
{
_log.TraceInformation($"Error when compute signature and it is : {ex.Message}");
_log.TraceInformation($"Additional info => stack trace : {ex.StackTrace}");
resultValue = InvoiceResult.CannotSignXmlFiles;
}
return resultValue;
}
public (InvoiceResult resultValue, X509Certificate2 cert) GetDefaultCertificateStoredOnTheCard()
{
var resultValue = InvoiceResult.Success;
using X509Store x509Store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
X509Store store = x509Store;
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
X509Certificate2Collection certs = store.Certificates.Find(X509FindType.FindByTimeValid, DateTime.Now, true);
certs = certs.Find(X509FindType.FindByThumbprint, Settings.Default.Thumbprint, true);
if (certs.Count == 0)
{
resultValue = InvoiceResult.CannotFindSignature;
}
X509Certificate2 cert = certs[0];
if (cert.HasPrivateKey)
{
// software cert
_ = cert.PrivateKey as RSACryptoServiceProvider;
}
else
{
// certificate from smartcard
CspParameters csp = new CspParameters(1, "Microsoft Base Smart Card Crypto Provider")
{
Flags = CspProviderFlags.UseDefaultKeyContainer
};
_ = new RSACryptoServiceProvider(csp);
}
return (resultValue, cert);
}
private InvoiceResult SignXmlDocumentWithCertificate(XmlDocument xmlDoc, X509Certificate2 cert)
{
InvoiceResult resultValue = InvoiceResult.Success;
SignedXml signedXml = new SignedXml(xmlDoc)
{
//we will sign it with private key
SigningKey = cert.PrivateKey
};
if (cert.PrivateKey == null)
{
resultValue = InvoiceResult.CannotSignXmlFiles;
// throw new ArgumentException("Please make sure the application for electronic signatures is installed, so the private key can be obtained from the smart card!");
}
Reference reference = new Reference
{
//sign the entire doc
Uri = ""
};
XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
reference.AddTransform(env);
signedXml.AddReference(reference);
//PublicKey part
RSACryptoServiceProvider rsaprovider = (RSACryptoServiceProvider)cert.PublicKey.Key;
RSAKeyValue rkv = new RSAKeyValue(rsaprovider);
KeyInfo keyInfo = new KeyInfo();
keyInfo.AddClause(new KeyInfoX509Data(cert));
//We add the public key here
keyInfo.AddClause(rkv);
signedXml.KeyInfo = keyInfo;
_log.TraceInformation($"Cert has private key or not? {cert.HasPrivateKey}");
signedXml.ComputeSignature();
// Get the XML representation of the signature and save
// it to an XmlElement object.
_log.TraceInformation($"It computes the signature succesfully");
XmlElement xmlDigitalSignature = signedXml.GetXml();
// Append the element to the XML document.
xmlDoc.DocumentElement.AppendChild(xmlDoc.ImportNode(xmlDigitalSignature, true));
_log.TraceInformation($"It appends the signature succesfully");
return resultValue;
}
It works fine on Release/Debug
but not in Publish
. It gets a popup
, asks for a PIN
and once the PIN
has been entered the docs are signed.
It gets to the signedxml.ComputeSignature
and it returns an error :
The operation was canceled by the user.
Here is the exception that has been thrown :
System.Security.Cryptography.CryptographicException.ThrowCryptographicException(Int32 hr) at System.Security.Cryptography.Utils.SignValue(SafeKeyHandle hKey, Int32 keyNumber, Int32 calgKey, Int32 calgHash, Byte[] hash, Int32 cbHash, ObjectHandleOnStack retSignature) at System.Security.Cryptography.Utils.SignValue(SafeKeyHandle hKey, Int32 keyNumber, Int32 calgKey, Int32 calgHash, Byte[] hash) at System.Security.Cryptography.RSACryptoServiceProvider.SignHash(Byte[] rgbHash, Int32 calgHash) at System.Security.Cryptography.Xml.SignedXml.ComputeSignature()
The only way I get this error on release/debug
is if I cancel the window which asks for a PIN
.
Is there another way to compute the signature
and apply it to the XML
? This is the only one I was able to find so far.
It potentially could be an IIS
setting, but I have tried various things to no avail. The certificate can be found if I require SSL
on Client Side
is ticked and set it to Accept
as in the image :
I have also tried exporting the private key
which I saw on various posts, however, because it is a smart card
I am unable to export the Private Key
, I can only use it which is what I am doing with my code.
Once I start this part of the application it asks initially for the certificate
and the PIN
and returns the same error. On subsequent attempts , it never asks for the PIN
or the certificate
.