4

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 :

enter image description here

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.

grozdeto
  • 1,201
  • 1
  • 13
  • 34
  • Please read you posting and better describe what are your issues. I t sound like the code works the first time and then later fails. I suspect that a cookie may be causing your issue, but need better description. – jdweng Nov 15 '19 at 11:36
  • Thank you jdweng. I have editted the question. Please let me know if it needs more clarification. – grozdeto Nov 15 '19 at 12:54
  • Does it work if you publish and then install on same machine that it was built on. Lets find out if it is a publish issue or not working on a IIS. On a IIS you only have GUEST privileges and cannot read/write to files. I assume the smart card is on the client machine and not IIS. Try building with ANY CPU. What version of Windows are on Client and Server. I've seen a number of issues with RSA when upgrading operating systems. There seems to be an issue of going from 32 bit encryption to 64 bit encryption. See : https://en.wikipedia.org/wiki/Comparison_of_TLS_implementations – jdweng Nov 15 '19 at 14:23
  • Hi jdweng. I have published it on the same machine that I am running Visual Studio. The smart card is inserted and it reads it succesfully. It gets the certificate and the private key as I print out the logs. Both OS are same obviously because I run the app on localhost in Visual Studio and the other on IIS. – grozdeto Nov 15 '19 at 14:30
  • Once you publish and install using setup.exe, it "should not" matter if the build machine is Win 7 and the deploy machine is Win 10. Right now I not completely sure that there isn't something with encryption in the Net library that is broken. I've seen a few issues recently and do not know but have not found the root cause. In one case the code broke upgrading from Net 4.2. My best guess is something with 32 bit encryption and 64 bit encryption in Net Library. Did you run setup.exe as an admin on the IIS. Does build and deploy machine have EXACTLY same version of Net? – jdweng Nov 15 '19 at 16:04
  • Hi jdweng. I do not have a setup.exe I just publish the project and it generates a bunch of dlls. I run the project on IIS, and add https binding to it. I set it to https://localhost:444 and when I browse to that location it shows that it works. I edit the Webconfig to use the thumbprint and save the logs and on the serverside of the app I tell it to communicate with the published app on the localhost:444. – grozdeto Nov 18 '19 at 10:08
  • The will not work unless the build machine and deploy machine have the exact same version of Net installed. Publishing and then running setup.exe installs (upgrade) dlls on deploy machine to allow application to run just like when you install purchased software. – jdweng Nov 18 '19 at 10:17
  • Jdweng it is a class library project. Both of them are running on the same version - .Net Framework 4.8 – grozdeto Nov 18 '19 at 11:01
  • You application has two pieces 1) Server 2) Client. Most of the application is running on the client side. The server only downloads a certificate. Where is the certificate stored? What credentials are needed to access the certificate? A client connecting to a IIS only has guest privileges and does not have access to the file system on IIS. The server is a service that will start automatically when server is started. Normally these services are run as system account without admin privileges. So nomally data is either stored in a database or on a network drive to allow user access. – jdweng Nov 18 '19 at 11:24
  • Hi jdweng. Most of the application is actually running on the server. The client side is only this class library that talks with the server and uses the digital signature. The certificate is stored on the smart card and is read from the client class library that is published. It uses that private key to compute signature and signs the document. – grozdeto Nov 18 '19 at 12:27
  • The webpage is actually running on the client. The webpage is downloaded from a URL when the request is made. I believe your error is due to code running on server instead of client. Any data that is collected on the client you have to post to server if the server needs the data. The server cannot bind to an object in the client. The certificate needs to be sent to server in a post. – jdweng Nov 18 '19 at 13:46
  • I am not sure I understand your question. It seems that you want to make a digital signature using a smartcard using a "webapp", but there is no html or javascript. I only see server code. Are you going to use a web page or an .net application installed on the client? What is the relationship between SSL + client certificates an digital signature? A web page / app cannot make a digital signature using SSL, nor can a web page use the private key of a smartcard for XML signature in any case due to browser security restrictions.I have not understood your question or you have taken a wrong path. – pedrofb Nov 19 '19 at 20:16
  • @pedrofb I just posted the code that I think it is relevant. They obviously click a button that points to a controller and the controller executes The relation between SSL and client certificates is that if SSL is turned off, it does not even look for the certificate on the card. The private key is used in ComputeSignature(), it is only read for that matter. I do not use the browser anywhere or save anything to it. – grozdeto Nov 20 '19 at 09:23
  • The smartcart is on the client side. The signature is made on the server side. The private key never leaves the smartcard, so your code cannot work outside localhost. SSL with client certificate allows authentication, but never digital signature. You can see alternatives here, but they completely change the architecture of your system https://stackoverflow.com/a/50208561/6371459 – pedrofb Nov 20 '19 at 09:33
  • Pedro, the client side is where the signing is happening. That's why we install this web app on the first place. It finds the cert locally, signs it and then sends the doc to the server. Everything is done on the app , not on the browser. The only communication initially is server telling the app installed on the client PC to run. Unfortunately, without this SSL option as in the screenshot above, it does not even find the certificate. – grozdeto Nov 20 '19 at 09:55
  • Ok @grozdeto, it was what I did not understand and asked in my first comment: You are signing with an installed application. I don't know why an SSL connection is needed to get the smartcard to work. Maybe the drivers? I can't help you with that ... – pedrofb Nov 20 '19 at 10:34
  • You just can't use a smart card. They are designed to protect the private key, first by limiting access to people that can physically access the card, secondly by prompting for the PIN. The latter can't work in a server scenario. You need a certificate that you can deploy to the local store. The canonical [is here](https://stackoverflow.com/questions/48064019/copying-a-certificate-from-a-smart-card-to-computer). – Hans Passant Nov 20 '19 at 22:15
  • @Hans I can easily recreate the cert and add it to local store, the problem is - the private key is missing and therefore I can't do anything with the certificate. – grozdeto Nov 21 '19 at 07:55
  • hi @grozdeto It looks like your iis process may not have access to the private key. Are you able to makesuree that the IIS user has access to the store and certificate private key? – Dai Bok Nov 25 '19 at 15:32
  • @DaiBok I have access to it because I am able to get to this part: SignedXml signedXml = new SignedXml(xmlDoc) { //we will sign it with private key SigningKey = cert.PrivateKey }; Unfortunately the ComputeSignature breaks with the exception above. – grozdeto Nov 25 '19 at 17:17

1 Answers1

0

Change app pool identity to windows account

Nitin Sawant
  • 7,278
  • 9
  • 52
  • 98