2

I have a simply method to access my HSM with Pkcs11Interop.

This is the function:

static public byte[] findTargetKeySValue(String label, String type, string command)
{
    try
    {
        string pkcs11LibraryPath = @"C:\Program Files\SafeNet\Protect Toolkit 5\Protect Toolkit C SDK\bin\hsm\cryptoki.dll";
        Utility.Logger("cryptoki dll path " + pkcs11LibraryPath, command);
        using (Pkcs11 pkcs11 = new Pkcs11(pkcs11LibraryPath, Inter_Settings.AppType))
        {
            // Find first slot with token present
            Slot slot = Inter_Helpers.GetUsableSlot(pkcs11);
            // Open RW session
            using (Session session = slot.OpenSession(SessionType.ReadOnly))
            {
                // Login as normal user
                session.Login(CKU.CKU_USER, Inter_Settings.NormalUserPin);
                // Prepare attribute template that defines search criteria
                List<ObjectAttribute> objectAttributes = new List<ObjectAttribute>();
                objectAttributes.Add(new ObjectAttribute(CKA.CKA_CLASS, CKO.CKO_SECRET_KEY));
                if (type == "DES")
                    objectAttributes.Add(new ObjectAttribute(CKA.CKA_KEY_TYPE, CKK.CKK_DES));
                else if (type == "DES2")
                    objectAttributes.Add(new ObjectAttribute(CKA.CKA_KEY_TYPE, CKK.CKK_DES2));
                else if (type == "DES3")
                    objectAttributes.Add(new ObjectAttribute(CKA.CKA_KEY_TYPE, CKK.CKK_DES3));
                objectAttributes.Add(new ObjectAttribute(CKA.CKA_LABEL, label));//PROVAK

                List<ObjectHandle> foundObjects = session.FindAllObjects(objectAttributes);
                var key = foundObjects[0];
                byte[] plainKeyValue = null;
                List<ObjectAttribute> readAttrsSensitive = session.GetAttributeValue(key, new List<CKA>() { CKA.CKA_SENSITIVE });
                if (!readAttrsSensitive[0].GetValueAsBool())
                {
                    Utility.Logger("findTargetKeySValue chiave " + label + " non senstive", command);
                    List<ObjectAttribute> readAttrs = session.GetAttributeValue(key, new List<CKA>() { CKA.CKA_VALUE });
                    if (readAttrs[0].CannotBeRead)
                        throw new Exception("Key cannot be exported");
                    else
                        plainKeyValue = readAttrs[0].GetValueAsByteArray();
                    //Console.WriteLine(ByteArrayToAsciiHEX(plainKeyValue));
                    session.Logout();
                    return plainKeyValue;
                }
                else
                {
                    Utility.Logger("findTargetKeySValue chiave " + label + " senstive", command);
                    Console.WriteLine("wrap/unwrap");
                    objectAttributes = new List<ObjectAttribute>();
                    objectAttributes.Add(new ObjectAttribute(CKA.CKA_CLASS, CKO.CKO_SECRET_KEY));
                    objectAttributes.Add(new ObjectAttribute(CKA.CKA_KEY_TYPE, CKK.CKK_DES3));
                    objectAttributes.Add(new ObjectAttribute(CKA.CKA_LABEL, "WRAPPING_KEY")); //WRAPPING_KEY WRK
                    foundObjects = session.FindAllObjects(objectAttributes);

                    var wrappingKey = foundObjects[0];
                    Mechanism m = new Mechanism(CKM.CKM_DES3_ECB);

                    var wrapped = session.WrapKey(m, wrappingKey, key);
                    //Console.WriteLine("wrapped " + ByteArrayToAsciiHEX(wrapped));

                    //Console.WriteLine(ByteArrayToAsciiHEX(session.Decrypt(m, wrappingKey, wrapped)));
                    var k = session.Decrypt(m, wrappingKey, wrapped); 
                    session.Logout();
                    return k;

                }
            }
        }
    }
    catch (Exception e)
    {
        //Console.WriteLine(e.ToSafeString());
        Utility.Logger("findTargetKeySValue " + e.ToSafeString(), command);
        return null;
    }
}

I have this method called within a socket server when it receives a call from the client.

To test it, I created a little program with a loop. In this loop, it sends about 3 requests every seconds to the server, which use Pkcs11Interop.

Let's call this tester program tester.exe. If I run tester.exe, everything seems to be ok. But, while the first tester.exe is running, I try to execute another instance of tester.exe, I get the error

Net.Pkcs11Interop.Common.Pkcs11Exception: Method C_Initialize returned 2147483907

in this specific line code:

using (Pkcs11 pkcs11 = new Pkcs11(pkcs11LibraryPath, Inter_Settings.AppType))

Why? Which is the problem?

UPDATE:

AppType is

public static AppType AppType = AppType.MultiThreaded;

and the settings init is:

static Inter_Settings()
{

    if (AppType == AppType.MultiThreaded)
    {
        InitArgs40 = new LLA40.CK_C_INITIALIZE_ARGS();
        InitArgs40.Flags = CKF.CKF_OS_LOCKING_OK;

        InitArgs41 = new LLA41.CK_C_INITIALIZE_ARGS();
        InitArgs41.Flags = CKF.CKF_OS_LOCKING_OK;

        InitArgs80 = new LLA80.CK_C_INITIALIZE_ARGS();
        InitArgs80.Flags = CKF.CKF_OS_LOCKING_OK;

        InitArgs81 = new LLA81.CK_C_INITIALIZE_ARGS();
        InitArgs81.Flags = CKF.CKF_OS_LOCKING_OK;
    }

    // Convert strings to byte arrays
    SecurityOfficerPinArray = ConvertUtils.Utf8StringToBytes(SecurityOfficerPin);
    NormalUserPinArray = ConvertUtils.Utf8StringToBytes(NormalUserPin);
    ApplicationNameArray = ConvertUtils.Utf8StringToBytes(ApplicationName);

    // Build PKCS#11 URI that identifies private key usable in signature creation tests
    Pkcs11UriBuilder pkcs11UriBuilder = new Pkcs11UriBuilder();
    pkcs11UriBuilder.ModulePath = Pkcs11LibraryPath;
    pkcs11UriBuilder.Serial = TokenSerial;
    pkcs11UriBuilder.Token = TokenLabel;
    pkcs11UriBuilder.PinValue = NormalUserPin;
    pkcs11UriBuilder.Type = CKO.CKO_PRIVATE_KEY;
    pkcs11UriBuilder.Object = ApplicationName;

    PrivateKeyUri = pkcs11UriBuilder.ToString();
}

UPDATE2:

public class InteropHSM
{
    private Pkcs11 _pkcs11 = null;
    private Slot _slot = null;

    public InteropHSM()
    {
        string pkcs11LibraryPath = @"C:\Program Files\SafeNet\Protect Toolkit 5\Protect Toolkit C SDK\bin\hsm\cryptoki.dll";
        _pkcs11 = new Pkcs11(pkcs11LibraryPath, Inter_Settings.AppType);
        _slot = Inter_Helpers.GetUsableSlot(_pkcs11);
    }

    public byte[] findTargetKeySValue(String label, String type, string command)
    {

        try
        {
            //string pkcs11LibraryPath = @"C:\Program Files\SafeNet\Protect Toolkit 5\Protect Toolkit C SDK\bin\hsm\cryptoki.dll";
            //Utility.Logger("cryptoki dll path " + pkcs11LibraryPath, command);
            //using (Pkcs11 pkcs11 = new Pkcs11(pkcs11LibraryPath, Inter_Settings.AppType))
            //{

                //Slot slot = Inter_Helpers.GetUsableSlot(_pkcs11);

                using (Session session = _slot.OpenSession(SessionType.ReadOnly))
                {
                    // Login as normal user
                    session.Login(CKU.CKU_USER, Inter_Settings.NormalUserPin);
                    // Prepare attribute template that defines search criteria
                    List<ObjectAttribute> objectAttributes = new List<ObjectAttribute>();
                    objectAttributes.Add(new ObjectAttribute(CKA.CKA_CLASS, CKO.CKO_SECRET_KEY));
                    if (type == "DES")
                        objectAttributes.Add(new ObjectAttribute(CKA.CKA_KEY_TYPE, CKK.CKK_DES));
                    else if (type == "DES2")
                        objectAttributes.Add(new ObjectAttribute(CKA.CKA_KEY_TYPE, CKK.CKK_DES2));
                    else if (type == "DES3")
                        objectAttributes.Add(new ObjectAttribute(CKA.CKA_KEY_TYPE, CKK.CKK_DES3));
                    objectAttributes.Add(new ObjectAttribute(CKA.CKA_LABEL, label));//PROVAK

                    List<ObjectHandle> foundObjects = session.FindAllObjects(objectAttributes);
                    var key = foundObjects[0];
                    byte[] plainKeyValue = null;
                    List<ObjectAttribute> readAttrsSensitive = session.GetAttributeValue(key, new List<CKA>() { CKA.CKA_SENSITIVE });
                    if (!readAttrsSensitive[0].GetValueAsBool())
                    {
                        Utility.Logger("findTargetKeySValue chiave " + label + " non senstive", command);
                        List<ObjectAttribute> readAttrs = session.GetAttributeValue(key, new List<CKA>() { CKA.CKA_VALUE });
                        if (readAttrs[0].CannotBeRead)
                            throw new Exception("Key cannot be exported");
                        else
                            plainKeyValue = readAttrs[0].GetValueAsByteArray();
                        //Console.WriteLine(ByteArrayToAsciiHEX(plainKeyValue));
                        session.Logout();
                        return plainKeyValue;
                    }
                    else
                    {
                        Utility.Logger("findTargetKeySValue chiave " + label + " senstive", command);
                        Console.WriteLine("wrap/unwrap");
                        objectAttributes = new List<ObjectAttribute>();
                        objectAttributes.Add(new ObjectAttribute(CKA.CKA_CLASS, CKO.CKO_SECRET_KEY));
                        objectAttributes.Add(new ObjectAttribute(CKA.CKA_KEY_TYPE, CKK.CKK_DES3));
                        objectAttributes.Add(new ObjectAttribute(CKA.CKA_LABEL, "WRAPPING_KEY")); //WRAPPING_KEY WRK
                        foundObjects = session.FindAllObjects(objectAttributes);

                        var wrappingKey = foundObjects[0];
                        Mechanism m = new Mechanism(CKM.CKM_DES3_ECB);

                        var wrapped = session.WrapKey(m, wrappingKey, key);
                        //Console.WriteLine("wrapped " + ByteArrayToAsciiHEX(wrapped));

                        Console.WriteLine(ByteArrayToAsciiHEX(session.Decrypt(m, wrappingKey, wrapped)));
                        var k = session.Decrypt(m, wrappingKey, wrapped); 
                        session.Logout();
                        return k;

                    }
                }
            //}
        }
        catch (Exception e)
        {
            //Console.WriteLine(e.ToSafeString());
            Utility.Logger("findTargetKeySValue " + e.ToSafeString(), command);
            return null;
        }
    }
    public static string ByteArrayToAsciiHEX(byte[] ba)
    {
        string hex = BitConverter.ToString(ba);
        return hex.Replace("-", "");
    }
}

Every time is called, the server instance the class above and call the method findTargetKeySValue. If the server receive concurrent requests, it fails the HSM interaction... but I'm getting crazy, the session is different every time, like the specification saiys.

UPDATE3

Thread t = new Thread(() => ih.findTargetKeySValue(label, type, command));
t.Start();
Thread tt = new Thread(() => ih.findTargetKeySValue(label, type, command));
tt.Start();
Thread ttt = new Thread(() => ih.findTargetKeySValue(label, type, command));
ttt.Start();
Thread tttt = new Thread(() => ih.findTargetKeySValue(label, type, command));
tttt.Start();
Thread ttttt = new Thread(() => ih.findTargetKeySValue(label, type, command));
ttttt.Start();

I created this simple snippet to test multithread (findTargetKeySValue is defined above) and it crash with the message "Method C_Initialize returned 2147483907". This code is vendor defined and is CKR_CRYPTOKI_UNUSABLE. I will use this for the next tests.

UPDATE4:

I changed the code in

public InteropHSM()
{
    string pkcs11LibraryPath = @"C:\Program Files\SafeNet\Protect Toolkit 5\Protect Toolkit C SDK\bin\hsm\cryptoki.dll";
    _pkcs11 = new Pkcs11(pkcs11LibraryPath, Inter_Settings.AppType);
    _slot = Inter_Helpers.GetUsableSlot(_pkcs11);
    session = _slot.OpenSession(SessionType.ReadOnly);
    session.Login(CKU.CKU_USER, Inter_Settings.NormalUserPin);
}

public byte[] findTargetKeySValue(String label, String type, string command)
{

    try
    {
            List<ObjectAttribute> objectAttributes = new List<ObjectAttribute>();
            objectAttributes.Add(new ObjectAttribute(CKA.CKA_CLASS, CKO.CKO_SECRET_KEY));
            if (type == "DES")
                objectAttributes.Add(new ObjectAttribute(CKA.CKA_KEY_TYPE, CKK.CKK_DES));
            else if (type == "DES2")
                objectAttributes.Add(new ObjectAttribute(CKA.CKA_KEY_TYPE, CKK.CKK_DES2));
            else if (type == "DES3")
                objectAttributes.Add(new ObjectAttribute(CKA.CKA_KEY_TYPE, CKK.CKK_DES3));
            objectAttributes.Add(new ObjectAttribute(CKA.CKA_LABEL, label));//PROVAK

            List<ObjectHandle> foundObjects = session.FindAllObjects(objectAttributes);
            var key = foundObjects[0];
            byte[] plainKeyValue = null;
            List<ObjectAttribute> readAttrsSensitive = session.GetAttributeValue(key, new List<CKA>() { CKA.CKA_SENSITIVE });
            if (!readAttrsSensitive[0].GetValueAsBool())
            {
                Utility.Logger("findTargetKeySValue chiave " + label + " non senstive", command);
                List<ObjectAttribute> readAttrs = session.GetAttributeValue(key, new List<CKA>() { CKA.CKA_VALUE });
                if (readAttrs[0].CannotBeRead)
                    throw new Exception("Key cannot be exported");
                else
                    plainKeyValue = readAttrs[0].GetValueAsByteArray();
                //Console.WriteLine(ByteArrayToAsciiHEX(plainKeyValue));
                session.Logout();
            Console.WriteLine(plainKeyValue);
            return plainKeyValue;
            }
            else
            {
                Utility.Logger("findTargetKeySValue chiave " + label + " senstive", command);
                Console.WriteLine("wrap/unwrap");
                objectAttributes = new List<ObjectAttribute>();
                objectAttributes.Add(new ObjectAttribute(CKA.CKA_CLASS, CKO.CKO_SECRET_KEY));
                objectAttributes.Add(new ObjectAttribute(CKA.CKA_KEY_TYPE, CKK.CKK_DES3));
                objectAttributes.Add(new ObjectAttribute(CKA.CKA_LABEL, "WRAPPING_KEY")); //WRAPPING_KEY WRK
                foundObjects = session.FindAllObjects(objectAttributes);

                var wrappingKey = foundObjects[0];
                Mechanism m = new Mechanism(CKM.CKM_DES3_ECB);

                var wrapped = session.WrapKey(m, wrappingKey, key);
                //Console.WriteLine("wrapped " + ByteArrayToAsciiHEX(wrapped));

                Console.WriteLine(ByteArrayToAsciiHEX(session.Decrypt(m, wrappingKey, wrapped)));
                var k = session.Decrypt(m, wrappingKey, wrapped);
                //session.Logout();
                return k;

        }
    }
    catch (Exception e)
    {
        Console.WriteLine(e.ToSafeString());
        Utility.Logger("findTargetKeySValue " + e.ToSafeString(), command);
        return null;
    }
}

I call it with code from UPDATE3. I'm getting Method C_FindObjectsFinal returned CKR_OPERATION_NOT_INITIALIZED when the code calls

List<ObjectHandle> foundObjects = session.FindAllObjects(objectAttributes);
Piero Alberto
  • 3,823
  • 6
  • 56
  • 108

1 Answers1

3

You are not using PKCS#11 API correctly in multithreaded application. This is a known issue.

Short answer is that you need to ensure that:

  • you are using single instance of Pkcs11 class in your application (i.e. loaded during server startup and unloaded during its stop)
  • you are using new instance of Session class for each cryptographic operation

Long answer is that you need to read "Chapter 6 - General overview" of PKCS#11 v2.20 specification which explains all basic concepts of PKCS#11 API. After you finish this mandatory reading, you can take a look at Pkcs11RsaSignature class in Pkcs11Interop.PDF project for a working code sample of class that can be used in multithreaded environment.

This is how you can fix your code from example 4:

public InteropHSM()
{
    string pkcs11LibraryPath = @"C:\Program Files\SafeNet\Protect Toolkit 5\Protect Toolkit C SDK\bin\hsm\cryptoki.dll";
    _pkcs11 = new Pkcs11(pkcs11LibraryPath, Inter_Settings.AppType);
    _slot = Inter_Helpers.GetUsableSlot(_pkcs11);
    session = _slot.OpenSession(SessionType.ReadOnly);
    session.Login(CKU.CKU_USER, Inter_Settings.NormalUserPin);
}

public byte[] findTargetKeySValue(String label, String type, string command)
{
    try
    {
        List<ObjectAttribute> objectAttributes = new List<ObjectAttribute>();
        objectAttributes.Add(new ObjectAttribute(CKA.CKA_CLASS, CKO.CKO_SECRET_KEY));
        if (type == "DES")
            objectAttributes.Add(new ObjectAttribute(CKA.CKA_KEY_TYPE, CKK.CKK_DES));
        else if (type == "DES2")
            objectAttributes.Add(new ObjectAttribute(CKA.CKA_KEY_TYPE, CKK.CKK_DES2));
        else if (type == "DES3")
            objectAttributes.Add(new ObjectAttribute(CKA.CKA_KEY_TYPE, CKK.CKK_DES3));
        objectAttributes.Add(new ObjectAttribute(CKA.CKA_LABEL, label));//PROVAK

        using (var session2 = _slot.OpenSession(SessionType.ReadOnly))
        {
            List<ObjectHandle> foundObjects = session2.FindAllObjects(objectAttributes);
            var key = foundObjects[0];
            byte[] plainKeyValue = null;
            List<ObjectAttribute> readAttrsSensitive = session2.GetAttributeValue(key, new List<CKA>() { CKA.CKA_SENSITIVE });
            if (!readAttrsSensitive[0].GetValueAsBool())
            {
                Utility.Logger("findTargetKeySValue chiave " + label + " non senstive", command);
                List<ObjectAttribute> readAttrs = session2.GetAttributeValue(key, new List<CKA>() { CKA.CKA_VALUE });
                if (readAttrs[0].CannotBeRead)
                    throw new Exception("Key cannot be exported");
                else
                    plainKeyValue = readAttrs[0].GetValueAsByteArray();
                Console.WriteLine(plainKeyValue);
                return plainKeyValue;
            }
            else
            {
                Utility.Logger("findTargetKeySValue chiave " + label + " senstive", command);
                Console.WriteLine("wrap/unwrap");
                objectAttributes = new List<ObjectAttribute>();
                objectAttributes.Add(new ObjectAttribute(CKA.CKA_CLASS, CKO.CKO_SECRET_KEY));
                objectAttributes.Add(new ObjectAttribute(CKA.CKA_KEY_TYPE, CKK.CKK_DES3));
                objectAttributes.Add(new ObjectAttribute(CKA.CKA_LABEL, "WRAPPING_KEY")); //WRAPPING_KEY WRK
                foundObjects = session2.FindAllObjects(objectAttributes);

                var wrappingKey = foundObjects[0];
                Mechanism m = new Mechanism(CKM.CKM_DES3_ECB);

                var wrapped = session2.WrapKey(m, wrappingKey, key);
                //Console.WriteLine("wrapped " + ByteArrayToAsciiHEX(wrapped));

                Console.WriteLine(ByteArrayToAsciiHEX(session2.Decrypt(m, wrappingKey, wrapped)));
                var k = session2.Decrypt(m, wrappingKey, wrapped);
                return k;
            }
        }
    }
    catch (Exception e)
    {
        Console.WriteLine(e.ToSafeString());
        Utility.Logger("findTargetKeySValue " + e.ToSafeString(), command);
        return null;
    }
}
jariq
  • 11,681
  • 3
  • 33
  • 52
  • You're right, I didn't read the specification. Now I read it until chapter 6. I'm reading your link about Pkcs11RsaSignature, but the code is quite long, which is the part to manage the multithread environment? Anyway, about your questions: 1) What do you mean with single instance of Pkcs11? Do you mean this? "Pkcs11 pkcs11 = new Pkcs11(pkcs11LibraryPath, Inter_Settings.AppType)" 2) Yes, I create new session every time I need access to the HSM – Piero Alberto Nov 30 '17 at 13:54
  • The calling process are many, but the process using the Pkcs11Interop is always the same. What you can see in the first code snippet is the code called by this server service. I tried to modify it to make it more similar to your sample, but the result is still the same. I also tried to set the method as non-static, but also here nothing changed. – Piero Alberto Nov 30 '17 at 15:30
  • With unique instance of Pkcs11 (and also slot, from your sample), what do you mean? I set a unique Pkcs11 in my class (look update), but nothing changed, maybe I have to create it in my server service? – Piero Alberto Nov 30 '17 at 15:33
  • Sorry the multiple comments, I'm doing a lot of tests and I write here the most important steps.. I made a simple program which call my function with some thread, to access at the same at the function and it gets the same error.. – Piero Alberto Nov 30 '17 at 16:40
  • @PieroAlberto Error `2147483907` dec / `0x80000103` hex is vendor defined error. You'll need to consult documentation provided by PKCS#11 library/device vendor to find its meaning. – jariq Nov 30 '17 at 19:57
  • @PieroAlberto code in update2 looks better. That means `var i = new InteropHSM();` needs to be called just once during server startup and `i.findTargetKeySValue(...);` for each client. BTW it's enough to perform session login just once in `InteropHSM` constructor and all latter clients will be logged in. – jariq Nov 30 '17 at 20:02
  • I tried following your hint and edit the code like update4, but I have now another problem, I'm getting the error Method C_FindObjectsFinal returned CKR_OPERATION_NOT_INITIALIZED (look the update for details) – Piero Alberto Dec 01 '17 at 08:19
  • @PieroAlberto you need to open new session for every operation in `findTargetKeySValue`. For object finding too. – jariq Dec 01 '17 at 08:24
  • there is really something I don't understand. you said I have to open session only once when service starts, but now you say I have to open a session for every operations.. I'm a little bit confused, if I have to open every time a new session, which is the utility of the session started when service starts? – Piero Alberto Dec 01 '17 at 08:31
  • 1
    @PieroAlberto I've updated my answer. You need to have one "global" session which keeps login state and then use new session for every client. – jariq Dec 01 '17 at 08:51
  • It seems to work... wonderful!!!!!! At least, the snippet in UPDATE3 does. Now I will make more test and I will update you.. thanks a lot! – Piero Alberto Dec 01 '17 at 09:11
  • Little question: the "global session" has something like a timeout or something similar? – Piero Alberto Dec 01 '17 at 09:16
  • 1
    @PieroAlberto usually it doesn't but it may be vendor/HSM specific so you will need to test it. – jariq Dec 01 '17 at 09:22
  • I know I set this as solution, but now I have another weird problem.. I try to send some request to the server and after a while I get an error: " CKR_CRYPTOKI_NOT_INITIALIZED" at this row "using (var session2 = _slot.OpenSession(SessionType.ReadOnly))"...... why? – Piero Alberto Dec 01 '17 at 10:02
  • After that error, a lot of requests fail, but not all... with the same message.. maybe does it make sense insert a check to see if the global session is valid? If yes, how? – Piero Alberto Dec 01 '17 at 10:12
  • For my last comment/error: in my tester code there was a call using the old code and not the new one. Excuse me and thanks again. Just to let you know, we will use Pkcs11Interop instead of NCryptoki from now! – Piero Alberto Dec 01 '17 at 13:18