0

I have wcf web service that signs pdf documents when there is a request from my website. Everything works fine locally trying to sign few documents while development.

Once my code was deployed to production environment, it was working fine for few hours but suddenly i have started received Error n 48 followed by n 224.

Message : Error n. 48 Source : NCryptoki Stack Trace : at Cryptware.NCryptoki.Session.Sign(Byte[] data) at iTextSharp.text.pdf.security.MakeSignature.SignDetached(PdfSignatureAppearance sap, IExternalSignature externalSignature, ICollection1 chain, ICollection1 crlList, IOcspClient ocspClient, ITSAClient tsaClient, Int32 estimatedSize, CryptoStandard sigtype)

Message : Error n. 224 Source : NCryptoki Stack Trace : at Cryptware.NCryptoki.Session.Sign(Byte[] data) at iTextSharp.text.pdf.security.MakeSignature.SignDetached(PdfSignatureAppearance sap, IExternalSignature externalSignature, ICollection1 chain, ICollection1 crlList, IOcspClient ocspClient, ITSAClient tsaClient, Int32 estimatedSize, CryptoStandard sigtype)

I struggling to understand what could be happening and how can i replicate this issue locally so i can try to fix it. I thought it could be load issue but according to logs server was only signing about 4-5 request per min.

Here is the snippet of my signing code.

1) WCF SOAP Action

    public void TestSignPDF()
    {

        string fileName = Guid.NewGuid().ToString();
        String src = @"C:\tmp\singlepage.pdf";
        String DEST = string.Format(@"C:\tmp\{0}.pdf", fileName);

        MemoryStream ms = new MemoryStream();

        FileStream file = new FileStream(src, FileMode.Open, FileAccess.Read);
        byte[] bytes = new byte[file.Length];
        file.Read(bytes, 0, (int)file.Length);
        ms.Write(bytes, 0, (int)file.Length);
        file.Close();
        ms.Seek(0, SeekOrigin.Begin);

        HSMSignDocument x = new HSMSignDocument();
        Stream result = x.SignDocument(ms, "334", fileName);

        if (result != null)
        {
            FileStream file2 = new FileStream(DEST, FileMode.Create, System.IO.FileAccess.Write);
            byte[] bytes2 = new byte[result.Length];
            result.Read(bytes2, 0, (int)result.Length);
            file2.Write(bytes2, 0, bytes2.Length);
            file2.Close();
            result.Close();
        }

        ms.Close();
    }

2.) HSMSignDocument class (static)

public class HSMSignDocument :  IDisposable
{
    private static Cryptoki cryptoki = null;
    private static Session session = null;
    private static NLogger LOGGER = new NLogger();
    private static readonly string HSMPartitionPwd = ConfigurationManager.AppSettings["HSMPartitionPassword"];

    private string TIME_STAMPING_SERVER_URL = "xx";
    private string TIME_STAMPING_SERVER_ACCOUNT = "xx"; 
    private string TIME_STAMPING_SERVER_PASSWORD = "xx"; 


    static HSMSignDocument()
    {
        Cryptoki.Licensee = "xxx";
        Cryptoki.ProductKey = "xxx";

        if (cryptoki == null)
        {
            cryptoki = new Cryptoki();
            cryptoki.Attach(@"C:\Program Files\SafeNet\LunaClient\cryptoki.dll");
            cryptoki.Initialize();
        }

        if (session == null)
        {
            // Reads the set of slots containing a token
            SlotList slots = cryptoki.Slots;
            if (slots.Count == 0)
            {
                LOGGER.Error("slots.Count == 0, no slots available");
                return;
            }

            // Gets the first slot available
            Slot slot = slots[0];
            if (!slot.IsTokenPresent)
            {
                LOGGER.Error("!slot.IsTokenPresent, no tokens present");
                return;;
            }

            // Gets the first token available
            Token token = slot.Token;

            // Opens a read/write serial session
            session = token.OpenSession(Session.CKF_SERIAL_SESSION | Session.CKF_RW_SESSION, null, null);

            int nRes = session.Login(Session.CKU_USER, HSMPartitionPwd);
            if (nRes != 0)
            {
                LOGGER.Error("Could not login to Session");
            }
        }
    }

    private  IList<Org.BouncyCastle.X509.X509Certificate> GetKeyChain()
    {
        //Private key pointers have been installed in 
        X509Store x509Store = new X509Store(StoreName.My, StoreLocation.LocalMachine);

        x509Store.Open(OpenFlags.ReadOnly);
        X509Certificate2Collection certificates = x509Store.Certificates;
        IList<Org.BouncyCastle.X509.X509Certificate> chain = new List<Org.BouncyCastle.X509.X509Certificate>();
        X509Certificate2 pk = null;

        string HSMCertSerialNumber = ConfigurationManager.AppSettings["HSMCertificateSerialNumber"];

        if (certificates.Count > 0)
        {
            foreach (X509Certificate2 mCert in x509Store.Certificates)
            {
                //GETS THE KEY WE ARE LOOKING - Private key for HSM
                if (String.Compare(mCert.SerialNumber, HSMCertSerialNumber, true) == 0) 
                {
                    pk = mCert;
                    break;
                }
            }

            X509Chain x509chain = new X509Chain();
            x509chain.Build(pk);

            foreach (X509ChainElement x509ChainElement in x509chain.ChainElements)
            {
                // Build up root chain
                chain.Add(DotNetUtilities.FromX509Certificate(x509ChainElement.Certificate)); 
            }
        }

        x509Store.Close();

        return chain; 
    }

    public bool ApplySignatureDetails(Session session, String alias, Stream inputFile, string tempWorkingFile)
    {
        IList<Org.BouncyCastle.X509.X509Certificate> chain = GetKeyChain();

        // creates a TSA client(Time Stampingserver)
        ITSAClient timeStampingClient = new TSAClientBouncyCastle(TIME_STAMPING_SERVER_URL, TIME_STAMPING_SERVER_ACCOUNT, TIME_STAMPING_SERVER_PASSWORD);

        IOcspClient ocspClient = new OcspClientBouncyCastle(); // Online Certificate Status Protocol  -  adobe requirement
        List<ICrlClient> crlList = new List<ICrlClient>(); //revocation list -  adobe requirement
        crlList.Add(new CrlClientOnline(chain));

        return PKCS11Signer.Sign(inputFile, String.Format(tempWorkingFile, alias), chain, session, alias, DigestAlgorithms.SHA256, CryptoStandard.CMS, "Doc Signed", "My Company", crlList, ocspClient, timeStampingClient, 0);
    }


    void IDisposable.Dispose()
    {
        if (session != null)
        {
            session.Logout();
            session.Close();
            session = null;
        }
        if (cryptoki != null)
        {
            cryptoki.Finalize(IntPtr.Zero);
            cryptoki = null;
        }
    }
}

3.) PKCS11Signer Class

class PKCS11Signer
{
    private static NLogger LOGGER = new NLogger();

    private static Semaphore Bouncer { get; set; }

    public static bool Sign(Stream inputFile, String dest, ICollection<Org.BouncyCastle.X509.X509Certificate> chain, Session session, String alias,
                     String digestAlgorithm, CryptoStandard subfilter, String reason, String location,
                     ICollection<ICrlClient> crlList, IOcspClient ocspClient, ITSAClient tsaClient, int estimatedSize)
    {

        // Creating the reader and the stamper
        PdfReader reader = null;
        PdfStamper stamper = null;
        FileStream outputStream = null;

        try
        {

            if (Bouncer ==  null)
            {
                Bouncer = new Semaphore(1, 1);
            }

            Bouncer.WaitOne();
            LOGGER.Info("Start Actual Signing");
            reader = new PdfReader(inputFile);

            outputStream = new FileStream(dest, FileMode.Create, FileAccess.Write);
            stamper = PdfStamper.CreateSignature(reader, outputStream, '\0');
            LOGGER.Info("PDFStamper Created");

            // Creating the appearance
            PdfSignatureAppearance appearance = stamper.SignatureAppearance;

            LOGGER.Info("PdfSignatureAppearance Created");

            appearance.Reason = reason;
            appearance.Location = location;

            //Uncomment to show signiture text
            //appearance.SetVisibleSignature(new iTextSharp.text.Rectangle(56, 128, 164, 160), 1, "signature");


            appearance.CertificationLevel = PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED;
            LOGGER.Info("PdfSignatureAppearance Created");

            // Creating the signature
            IExternalSignature pks = new PrivateKeySignature(session, alias);
            LOGGER.Info("Encryption algorithm " + pks.GetEncryptionAlgorithm());
            LOGGER.Info("Hash algorithm " + pks.GetHashAlgorithm());
            LOGGER.Info("Hash code " + pks.GetHashCode());
            LOGGER.Info("pks : " + pks.ToString());


            LOGGER.Info("IExternalSignature Created perform sign");
            MakeSignature.SignDetached(appearance, pks, chain, crlList, ocspClient, tsaClient, estimatedSize, subfilter);
            //System.Threading.Thread.Sleep(1000); //Globalsign requirement

            LOGGER.Info("MakeSignature.SignDetached Completed");
        }
        catch (Exception ex)
        {
            LOGGER.Error(ex);
            throw ex;
        }
        finally
        {
             Bouncer.Release();
            if (stamper != null)
                stamper.Close();
            if (reader != null)
                reader.Close();
            if (outputStream != null)
                outputStream.Close();
            if (inputFile != null)
                inputFile.Close();
        }
        return true;
    }
}

}

4.) PrivateKeySignature Class

class PrivateKeySignature : IExternalSignature
{

    private static NLogger LOGGER = new NLogger();
    private static ObjectCache cache = MemoryCache.Default;

    private readonly Session session;
    RSAPrivateKey privateKey;

    public PrivateKeySignature(Session session, String alias)
    {
        string HSMPrivateKeyCertReq = ConfigurationManager.AppSettings["HSMPrivateKeyCertReq"];
        this.session = session;
        LOGGER.Info("Starting to get PrivateKeySignature");
        CryptokiCollection pkobjects = findTarget(session, HSMPrivateKeyCertReq);

        LOGGER.Info("Search session for private key using template");

        foreach(var keyObject in pkobjects)
        {
            LOGGER.Info("RSA/CertReq:" + HSMPrivateKeyCertReq);
            privateKey = (RSAPrivateKey)keyObject;
        }

        if (privateKey == null)
        {
            throw new Exception("privateKey not found");
        }

        LOGGER.Info("Private Key Found");
    }

    public String GetHashAlgorithm()
    {
        return "SHA1";
    }

    public String GetEncryptionAlgorithm()
    {
        return "RSA";
    }

    public byte[] Sign(byte[] message)
    {
        LOGGER.Info("Start apply Private Key");
        session.SignInit(Mechanism.SHA1_RSA_PKCS, privateKey);
        LOGGER.Info("End apply Private Key");
        return session.Sign(message);
    }

    public CryptokiCollection findTarget(Session session, string label)
    {
        if (cache.Contains(label))
        {
            return (CryptokiCollection)cache.Get(label);
        }
        else
        {
            CryptokiCollection template = new CryptokiCollection();

            template.Add(new ObjectAttribute(ObjectAttribute.CKA_LABEL, label));
            template.Add(new ObjectAttribute(ObjectAttribute.CKA_CLASS, CryptokiObject.CKO_PRIVATE_KEY));
            template.Add(new ObjectAttribute(ObjectAttribute.CKA_KEY_TYPE, Key.CKK_RSA));

            var pk = session.Objects.Find(template, 1);

            // Store data in the cache
            CacheItemPolicy cacheItemPolicy = new CacheItemPolicy();
            cacheItemPolicy.SlidingExpiration = TimeSpan.FromHours(4);
            cache.Add(label, pk, cacheItemPolicy);

            return pk;
        }
    }
}

}

user1754675
  • 887
  • 13
  • 32

1 Answers1

1

NCryptoki returns back the errors from the underlying PKCS#11 module.

The list of PKCS#11 errors is available here:

http://wiki.ncryptoki.com/How-NCryptoki-manages-PKCS-11-errors.ashx

In your case 48 (0x30) means: CKR_DEVICE_ERROR

while 224 (0xE0) means: CKR_TOKEN_NOT_PRESENT

it seems as there is a configuration problem on your production HSM

Ugo Chirico
  • 347
  • 3
  • 6
  • I am caching private key in PrivateKeySignature Class - findTarget. Could this have caused the problem ? When i try to find target it takes more then 1s which adds that to signing. Is there a better way of doing this ? – user1754675 Nov 08 '17 at 16:12
  • I think that the caching has nothing to do with the problem. How do you search for the target? – Ugo Chirico Nov 11 '17 at 19:05
  • The PrivateKeySignature Class, there is a method FindTarget. – user1754675 Nov 13 '17 at 15:31
  • Ok. I understand now. It is a good approach, but consider that the handle to a PKCS#11 object lives in a process. DIfferent processes may have different handles – Ugo Chirico Nov 14 '17 at 16:24
  • Is there a better approach so if it is different processes then it reset cache so we get a performance benefit.? – user1754675 Nov 14 '17 at 17:28