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:
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;
}
}
}
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.