digital-signaturepkcs#11hsmpkcs11interopsofthsm

Pkcs11Exception: Method C_GetSessionInfo returned CKR_CRYPTOKI_NOT_INITIALIZED


We are using Thales nShield HSM for storing Private keys and the corresponding public key is stored in the Certificate store.

We have written the logic as below:

  1. Search for a valid slot and open a session for that on the first call and it can serve multiple requests.
  2. It expires after half an hours. During the time it is not expired, if it gets any request to serve.
  3. Now even when it is not expired, when we try to check the sessionInfo it is giving the below message:

Method C_GetSessionInfo returned CKR_CRYPTOKI_NOT_INITIALIZED

Please help. Thanks in Advance.

The below is the addition to the above query.

We created a class, which encapsulated all of the Pkcs11Interop usage and exposed few methods as shown below.

    /// <summary>
    /// Contains the information about Private key stored in HMS and Certificate to load from File System/Windows Certificates Store/HSM.
    /// </summary>
    public class HardwareSecureModule
    {
        /// <summary>
        /// CryptoApi reference
        /// </summary>
        public string CryptoApiPath { get; set; }

        /// <summary>
        /// Idenfitier of the Private Key
        /// </summary>
        public string KeyLabel { get; set; }

        /// <summary>
        /// Idenfitier type of the Private Key
        /// </summary>
        public string KeyIdentifier { get; set; }

        /// <summary>
        /// Idenfitier of the Token
        /// </summary>
        public string TokenLabel { get; set; }

        /// <summary>
        /// Token Pin 
        /// </summary>
        public string TokenPin { get; set; }

        /// <summary>
        /// Idenfitier of the Certificate
        /// </summary>
        public string CertificateLabel { get; set; }        
    }

    public interface IHsmSession : IDisposable
    {
        /// <summary>
        /// Find key encryption algorithm
        /// </summary>
        /// <returns></returns>
        string GetEncryptionAlgorithm();

        /// <summary>
        /// sign the digest
        /// </summary>
        /// <param name="digest"></param>
        /// <returns></returns>
        byte[] Sign(byte[] digest, string encryptionAlgorithm, string hashAlgorithm);

        /// <summary>
        /// Indicates if thread within the pool is working
        /// to avoid disposal of the same
        /// </summary>
        bool Locked { get; set; }

        /// <summary>
        /// Unique identifier of the HSM Session
        /// </summary>
        Guid Id { get; }
    }
    /// <summary>
    /// Class for communicating with HSM
    /// </summary>
    public class Pkcs11HsmSession : IHsmSession
    {
        private Pkcs11 _pkcs11;
        private Slot _slot;
        private Session _session;
        private readonly HardwareSecureModule _certificateInformation = null;

        public bool Locked { get; set; }
        public Guid Id { get; }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="certificateInformation"></param>
        public Pkcs11HsmSession(HardwareSecureModule certificateInformation)
        {
            Id = Guid.NewGuid();
            _certificateInformation = certificateInformation;
            if (_certificateInformation != null)
                InitializeVariables();
        }

        private void InitializeVariables()
        {
            _pkcs11 = GetPkcs11Instance(_certificateInformation.CryptoApiPath);
            if (_pkcs11 == null)
                throw new Exception("Unable to create instance of Pkcs11");
            _slot = FindSlot(_pkcs11, _certificateInformation.TokenLabel);
            if (_slot == null)
                throw new Exception("Specified token not found: " + _certificateInformation.TokenLabel);
            _session = _slot.OpenSession(true);
            if (_session == null)
                throw new Exception("Unable to create session for the slot");
            SessionLogin();            
        }

        private Pkcs11 GetPkcs11Instance(string hsmCryptoApi)
        {
            Pkcs11 pkcs11 = null;
            try
            {
                pkcs11 = CreatePkcs11Instance(hsmCryptoApi, true);
            }
            catch (Pkcs11Exception ex)
            {
                if (ex.RV == CKR.CKR_CANT_LOCK)
                    pkcs11 = CreatePkcs11Instance(hsmCryptoApi, false);
                else
                    throw ex;
            }
            return pkcs11;
        }

        private Pkcs11 CreatePkcs11Instance(string hsmCryptoApi, bool useOsLocking)
        {
            return new Pkcs11(hsmCryptoApi, useOsLocking);
        }

        private Slot FindSlot(Pkcs11 pkcs11, string tokenLabel)
        {
            if (string.IsNullOrEmpty(tokenLabel))
                throw new Exception("Token label is not specified");

            List<Slot> slots = pkcs11.GetSlotList(true);            

            if (slots != null && slots.Count > 0)
            {
                foreach (Slot slot in slots)
                {
                    TokenInfo tokenInfo = null;

                    try
                    {
                        tokenInfo = slot.GetTokenInfo();                        
                    }
                    catch (Pkcs11Exception ex)
                    {
                        if (ex.RV != CKR.CKR_TOKEN_NOT_RECOGNIZED && ex.RV != CKR.CKR_TOKEN_NOT_PRESENT)
                            throw;
                    }

                    if (tokenInfo == null)
                        continue;

                    if (!string.IsNullOrEmpty(tokenLabel))
                        if (0 !=
                            String.Compare(tokenLabel, tokenInfo.Label, StringComparison.InvariantCultureIgnoreCase))
                            continue;
                    return slot;
                }
            }
            return null;
        }

        /// <summary>
        /// HSM Signs the digest using private key 
        /// </summary>
        /// <param name="message"></param>
        /// <param name="encryptionAlgorithm"></param>
        /// <param name="hashAlgorithm"></param>
        /// <returns></returns>
        public virtual byte[] Sign(byte[] message, string encryptionAlgorithm, string hashAlgorithm)
        {
            hashAlgorithm = hashAlgorithm.Replace("-", string.Empty);

            CKM signingMechanismType = GetSigningMechanismType(encryptionAlgorithm, hashAlgorithm);
            SessionLogin();

            ObjectHandle privateKeyHandle = GetPrivateKeyHandle();
            if (signingMechanismType == CKM.CKM_ECDSA)
            {
                message = GetMessageDigest(message, hashAlgorithm);
            }

            using (Mechanism mechanism = new Mechanism(signingMechanismType))
            {
                byte[] signedHash = _session.Sign(mechanism, privateKeyHandle, message);
                if (signingMechanismType == CKM.CKM_ECDSA)
                {
                    return ConstructEcdsaSigValue(signedHash);
                }

                return signedHash;
            }
        }

        private byte[] GetMessageDigest(byte[] message, string hashAlgorithm)
        {
            CKM hashMechanismType = (CKM)Enum.Parse(typeof(CKM), "CKM_" + hashAlgorithm.ToUpper());
            using (Mechanism mechanism = new Mechanism(hashMechanismType))
            {
                return _session.Digest(mechanism, message);
            }
        }

        /// <summary>
        /// Construct ECDSA der sequence
        /// </summary>
        /// <param name="rs"></param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ArgumentException"></exception>
        public byte[] ConstructEcdsaSigValue(byte[] rs)
        {
            if (rs == null)
                throw new ArgumentNullException("rs is null");

            if (rs.Length < 2 || rs.Length % 2 != 0)
                throw new ArgumentException("Invalid length of rs byte");

            int halfLen = rs.Length / 2;

            byte[] half1 = new byte[halfLen];
            Array.Copy(rs, 0, half1, 0, halfLen);
            var r = new BigInteger(1, half1);

            byte[] half2 = new byte[halfLen];
            Array.Copy(rs, halfLen, half2, 0, halfLen);
            var s = new BigInteger(1, half2);

            var derSequence = new Org.BouncyCastle.Asn1.DerSequence(
                new Org.BouncyCastle.Asn1.DerInteger(r),
                new Org.BouncyCastle.Asn1.DerInteger(s));

            return derSequence.GetDerEncoded();
        }

        /// <summary>
        /// GetEncryptionAlgorithm for Interface
        /// </summary>
        /// <returns></returns>
        public string GetEncryptionAlgorithm()
        {
            SessionLogin();
            string objectAttributeValue = GetObjectAttribute().ToString();            

            switch ((CKK)Enum.Parse(typeof(CKK), objectAttributeValue))
            {
                case CKK.CKK_RSA:
                    return "RSA";

                case CKK.CKK_ECDSA: //CKK.CKK_EC has same value as CKK.CKK_ECDSA:
                    return "ECDSA";
                default:
                    throw new Exception("Unknown Encryption Algorithm");
            }
        }

        /// <summary>
        /// Get atrributes for object handle
        /// </summary>
        /// <returns></returns>
        private ulong GetObjectAttribute()
        {
            ObjectHandle objectHandle = GetPrivateKeyHandle();

            List<CKA> keyAttributes = new List<CKA>();
            keyAttributes.Add(CKA.CKA_KEY_TYPE);
            List<ObjectAttribute> keyObjectAttributes = _session.GetAttributeValue(objectHandle, keyAttributes);

            return keyObjectAttributes[0].GetValueAsUlong();
        }

        /// <summary>
        /// Extract private key handle from HSM
        /// </summary>
        /// <returns></returns>
        private ObjectHandle GetPrivateKeyHandle()
        {
            _logger.WriteTrace("Inside GetPrivateKeyHandle()", LogCategory.General);

            string keyLabel = _certificateInformation.KeyLabel;
            string keyIdentifier = _certificateInformation.KeyIdentifier;
            List<ObjectAttribute> searchTemplate = new List<ObjectAttribute>();
            searchTemplate.Add(new ObjectAttribute(CKA.CKA_CLASS, CKO.CKO_PRIVATE_KEY));

            CKA indentifierType;
            bool parseResult = Enum.TryParse(keyIdentifier, out indentifierType);
            if (!parseResult)
                throw new Exception("Invalid Key Identifier '" + keyIdentifier + "'. Please provide a valid value (CKA_ID, CKA_LABEL etc).");
            searchTemplate.Add(new ObjectAttribute(indentifierType, keyLabel));

            List<ObjectHandle> foundObjects = _session.FindAllObjects(searchTemplate);
            if (foundObjects.Count < 1)
            {
                throw new Exception(string.Format("Private key with {0} '{1}' was not found", keyIdentifier, keyLabel));
            }
            else if (foundObjects.Count > 1)
            {
                throw new Exception(string.Format("More than one private key with {0} '{1}' was found", keyIdentifier, keyLabel));
            }

            return foundObjects[0];
        }

        /// <summary>
        /// Get MechanismType CKM for Ecdsa
        /// </summary>
        /// <param name="hashAlgorithm"></param>
        /// <returns></returns>
        private CKM GetEcdsaMechanismType(string hashAlgorithm)
        {
            switch (hashAlgorithm)
            {
                //Currently we don't have direct support for the below mechanism in HSM, however if supported this code can be uncommented and used
                //case "SHA1":
                //    return CKM.CKM_ECDSA_SHA1;
                //case "SHA224":
                //    return CKM.CKM_ECDSA_SHA224;
                //case "SHA256":
                //    return CKM.CKM_ECDSA_SHA256;
                //case "SHA384":
                //    return CKM.CKM_ECDSA_SHA384;
                //case "SHA512":
                //    return CKM.CKM_ECDSA_SHA512;
                default:
                    return CKM.CKM_ECDSA;
            }
        }

        /// <summary>
        /// Get CKM based upon hash algorithm
        /// </summary>
        /// <param name="hashAlgorithm"></param>
        /// <returns></returns>
        private CKM GetRsaMechanismType(string hashAlgorithm)
        {
            switch (hashAlgorithm)
            {
                case "SHA512":
                    return CKM.CKM_SHA512_RSA_PKCS;
                case "SHA256":
                default:
                    return CKM.CKM_SHA256_RSA_PKCS;
            }
        }

        /// <summary>
        /// Get CKM based on encryption and hash algorithm
        /// </summary>
        /// <param name="encryptionAlgorithm"></param>
        /// <param name="hashAlgorithm"></param>
        /// <returns></returns>
        private CKM GetSigningMechanismType(string encryptionAlgorithm, string hashAlgorithm)
        {
            switch (encryptionAlgorithm)
            {
                case "EC":
                case "ECDSA":
                    return GetEcdsaMechanismType(hashAlgorithm);
                case "RSA":
                default:
                    return GetRsaMechanismType(hashAlgorithm);
            }
        }

        private void CloseSession()
        {
            if (_session != null)
            {
                try
                {
                    SessionLogout();
                }
                catch
                {
                    // Any exceptions can be safely ignored here
                }

                _session.Dispose();
                _session = null;
            }
            _slot = null;
            if (_pkcs11 != null)
            {
                _pkcs11.Dispose();
                _pkcs11 = null;
            }
        }

        public void Dispose()
        {
            CloseSession();
        }

        private void SessionLogout()
        {
            if (_session != null && GetSessionState() == CKS.CKS_RO_USER_FUNCTIONS)
            {
                ulong sessionId = _session.SessionId;
                _session.Logout();                
            }
        }

        private void SessionLogin()
        {
            if (_session != null && GetSessionState() != CKS.CKS_RO_USER_FUNCTIONS)
            {
                _session.Login(CKU.CKU_USER, _certificateInformation.TokenPin);                
            }
        }

        private CKS GetSessionState()
        {
            try
            {
                return _session.GetSessionInfo().State;
            }
            catch (Exception ex)
            {
                if (_certificateInformation != null)
                    InitializeVariables();
                return _session.GetSessionInfo().State;
            }
        }
    }

Solution

  • PKCS#11 defines an application as a single process with single address space and one or multiple threads of control running in it.

    Any application becomes a "Cryptoki application" by initializing PKCS#11 library in one of its threads with a call to C_Initialize function. After the library has been initialized, the application can call other functions of PKCS#11 API. When the application is done using PKCS#11 API, it finalizes PKCS#11 library with a call to C_Finalize function and ceases to be a "Cryptoki application". From application perspective, PKCS#11 library initialization and finalization are global events, so it is crucial to ensure that one thread does not finalize library while other threads are still working with it.

    PKCS#11 function C_Initialize is called in constructor of HighLevelAPI.Pkcs11 class and C_Finalize function is called when instance of HighLevelAPI.Pkcs11 class is disposed. It is crucial to ensure that two instances of this class working with same PKCS#11 library do not overlap each other. My guess is that you are using more than one instance and you dispose it while you are still trying to use the other.