问题
I am using PKCS11Interop to perform Key Management operations inside an HSM. The HSM I am using is Thales PCI Express. Below is the class that wraps all operations being performed in HSM:
public sealed class KeyStoreOperations
{
private KeyStoreContext m_keyStoreContext;
private static Pkcs11 m_Pkcs11;
private static readonly object _syncLockPkcs11 = new object();
private static readonly object _syncLockHSMLogin = new object();
public KeyStoreOperations(KeyStoreContext keyStoreContext)
{
m_keyStoreContext = keyStoreContext;
InitializePkcs11Object();
}
/// <summary>
/// Generates key in the key store
/// </summary>
/// <param name="keyName"></param>
/// <returns></returns>
public void GenerateKey(string keyName)
{
HSMTransactionHandler((Session session) =>
{
Mechanism mechanism = new Mechanism(CKM.CKM_RSA_PKCS_KEY_PAIR_GEN);
ObjectHandle publicKeyHandle = null;
ObjectHandle privateKeyHandle = null;
byte[] ckaId = session.GenerateRandom(20);
List<ObjectAttribute> publicKeyAttributes = CreatePublicKeyTemplate(keyName, ckaId);
List<ObjectAttribute> privateKeyAttributes = CreatePrivateKeyTemplate(keyName, ckaId);
session.GenerateKeyPair(mechanism, publicKeyAttributes, privateKeyAttributes, out publicKeyHandle, out privateKeyHandle);
});
}
/// <summary>
/// Destroys key in the key store
/// </summary>
/// <param name="keyLabel"></param>
/// <returns></returns>
public void DestroyKey(string keyName)
{
HSMTransactionHandler((Session session) =>
{
var publicKeyHandle = GetPublicKey(keyName, session);
var privateKeyHandle = GetPrivateKey(keyName, session);
if (publicKeyHandle != null && privateKeyHandle != null)
{
session.DestroyObject(publicKeyHandle);
session.DestroyObject(privateKeyHandle);
}
});
}
/// <summary>
/// Encrypts a message using the key in the key store
/// </summary>
/// <param name="keyName"></param>
/// <param name="message"></param>
/// <returns></returns>
public string Encrypt(string keyName, string message)
{
ValidateInputs(message, "Message");
var encryptedMessage = string.Empty;
HSMTransactionHandler((Session session) =>
{
Mechanism mechanism = new Mechanism(CKM.CKM_RSA_PKCS);
var publicKey = GetPublicKey(keyName, session);
if (publicKey == null)
throw new HSMException(ErrorConstant.HSM_ENCRYPTION_KEY_NOT_FOUND);
var originalKeyBytes = EncryptionHelper.Decode(message);
var encryptedKeyBytes = session.Encrypt(mechanism, publicKey, originalKeyBytes);
encryptedMessage = EncryptionHelper.Encode(encryptedKeyBytes);
});
return encryptedMessage;
}
/// <summary>
/// Decrypts a key using the key in the key store
/// </summary>
/// <param name="keyName"></param>
/// <param name="cipher"></param>
/// <returns></returns>
public string Decrypt(string keyName, string cipher)
{
ValidateInputs(cipher, "Cipher");
var decryptedMessage = string.Empty;
HSMTransactionHandler((Session session) =>
{
Mechanism mechanism = new Mechanism(CKM.CKM_RSA_PKCS);
var privateKey = GetPrivateKey(keyName, session);
if (privateKey == null)
throw new HSMException(ErrorConstant.HSM_ENCRYPTION_KEY_NOT_FOUND);
var encryptedSymmetricKeyBytes = EncryptionHelper.Decode(cipher);
var decryptedSymmetricKeyBytes = session.Decrypt(mechanism, privateKey, encryptedSymmetricKeyBytes);
decryptedMessage = EncryptionHelper.Encode(decryptedSymmetricKeyBytes);
});
return decryptedMessage;
}
#region Private methods
#region Validations
private void ValidateInputs(string input, string name)
{
if (string.IsNullOrEmpty(input))
throw new ArgumentNullException(name);
}
#endregion Validations
private void HSMTransactionHandler(Action<Session> action)
{
Slot hsmSlot = null;
Session hsmSession = null;
try
{
hsmSlot = GetSlot(m_keyStoreContext.ModuleToken);
hsmSession = hsmSlot.OpenSession(false);
lock (_syncLockHSMLogin)
{
hsmSession.Login(CKU.CKU_USER, m_keyStoreContext.SecurityPin);
action(hsmSession);
hsmSession.Logout();
}
}
catch (Pkcs11Exception ex)
{
HandleHSMErrors(ex);
}
finally
{
if (!(hsmSession == null))
hsmSession.CloseSession();
}
}
private ObjectHandle GetPrivateKey(string keyName, Session session)
{
ObjectHandle privateKey = null;
List<ObjectHandle> foundObjects = null;
List<ObjectAttribute> objectAttributes = new List<ObjectAttribute>();
objectAttributes.Add(new ObjectAttribute(CKA.CKA_LABEL, keyName));
objectAttributes.Add(new ObjectAttribute(CKA.CKA_PRIVATE, true));
foundObjects = session.FindAllObjects(objectAttributes);
if (foundObjects != null && foundObjects.Count > 0)
{
privateKey = foundObjects[0];
}
return privateKey;
}
private ObjectHandle GetPublicKey(string keyName, Session session)
{
ObjectHandle publicKey = null;
List<ObjectHandle> foundObjects = null;
List<ObjectAttribute> objectAttributes = new List<ObjectAttribute>();
objectAttributes.Add(new ObjectAttribute(CKA.CKA_LABEL, keyName));
objectAttributes.Add(new ObjectAttribute(CKA.CKA_PRIVATE, false));
foundObjects = session.FindAllObjects(objectAttributes);
if (foundObjects != null && foundObjects.Count > 0)
{
publicKey = foundObjects[0];
}
return publicKey;
}
private List<ObjectAttribute> CreatePublicKeyTemplate(string keyName, byte[] ckaId)
{
List<ObjectAttribute> publicKeyAttributes = new List<ObjectAttribute>();
publicKeyAttributes.Add(new ObjectAttribute(CKA.CKA_TOKEN, true));
publicKeyAttributes.Add(new ObjectAttribute(CKA.CKA_PRIVATE, false));
publicKeyAttributes.Add(new ObjectAttribute(CKA.CKA_LABEL, keyName));
publicKeyAttributes.Add(new ObjectAttribute(CKA.CKA_ID, ckaId));
publicKeyAttributes.Add(new ObjectAttribute(CKA.CKA_ENCRYPT, true));
publicKeyAttributes.Add(new ObjectAttribute(CKA.CKA_VERIFY, true));
publicKeyAttributes.Add(new ObjectAttribute(CKA.CKA_VERIFY_RECOVER, true));
publicKeyAttributes.Add(new ObjectAttribute(CKA.CKA_WRAP, true));
publicKeyAttributes.Add(new ObjectAttribute(CKA.CKA_MODULUS_BITS, Convert.ToUInt64(m_keyStoreContext.KeySize)));
publicKeyAttributes.Add(new ObjectAttribute(CKA.CKA_PUBLIC_EXPONENT, new byte[] { 0x01, 0x00, 0x01 }));
return publicKeyAttributes;
}
private List<ObjectAttribute> CreatePrivateKeyTemplate(string keyName, byte[] ckaId)
{
List<ObjectAttribute> privateKeyAttributes = new List<ObjectAttribute>();
privateKeyAttributes.Add(new ObjectAttribute(CKA.CKA_TOKEN, true));
privateKeyAttributes.Add(new ObjectAttribute(CKA.CKA_PRIVATE, true));
privateKeyAttributes.Add(new ObjectAttribute(CKA.CKA_LABEL, keyName));
privateKeyAttributes.Add(new ObjectAttribute(CKA.CKA_ID, ckaId));
privateKeyAttributes.Add(new ObjectAttribute(CKA.CKA_SENSITIVE, true));
privateKeyAttributes.Add(new ObjectAttribute(CKA.CKA_DECRYPT, true));
privateKeyAttributes.Add(new ObjectAttribute(CKA.CKA_SIGN, true));
privateKeyAttributes.Add(new ObjectAttribute(CKA.CKA_SIGN_RECOVER, true));
privateKeyAttributes.Add(new ObjectAttribute(CKA.CKA_UNWRAP, true));
return privateKeyAttributes;
}
private Slot GetSlot(string tokenLabel)
{
Slot matchingSlot = null;
List<Slot> slots = m_Pkcs11.GetSlotList(true);
matchingSlot = slots[0];
if (tokenLabel != null)
{
matchingSlot = null;
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(m_keyStoreContext.ModuleToken))
if (0 != string.Compare(m_keyStoreContext.ModuleToken, tokenInfo.Label, StringComparison.Ordinal))
continue;
matchingSlot = slot;
break;
}
if (matchingSlot == null)
throw new HSMException(string.Format(ErrorConstant.HSM_CONFIGURATION_ERROR_INCORRECT_SLOT, tokenLabel));
}
return matchingSlot;
}
private void InitializePkcs11Object()
{
if (m_Pkcs11 == null)
{
lock (_syncLockPkcs11)
{
m_Pkcs11 = new Pkcs11(m_keyStoreContext.PKCS11LibraryPath, true);
}
}
}
private void HandleHSMErrors(Pkcs11Exception ex)
{
if (ex.RV == CKR.CKR_PIN_INCORRECT)
{
throw new HSMException(ErrorConstant.HSM_CONFIGURATION_ERROR_PIN_INCORRECT, ex);
}
else
{
throw new HSMException(ErrorConstant.HSM_CONFIGURATION_ERROR_GENERIC, ex);
}
}
#endregion
}
If you notice I am making use of two objects to apply locks. Object _syncLockPkcs11 is being used to implement singleton on m_Pkcs11 and _syncLockHSMLogin is used to synchronize Login to HSM . Earlier when I did not have these locks in place, I used to get the following errors from HSM, CKU_USER_ALREADY_LOGGED_IN and CKR_FUNCTION_FAILED. I implemented this changes based on the information provided in this link and in the section 6.7.7 Example of use of sessions of this document i.e.
With my current Implementation I do not get any of these errors but would like to know expert opinion here.
Some of the questions that I have are:
Is it OK to use m_Pkcs11 in this fashion i.e. not disposing it through out the process life cycle?
Is it Ok to apply lock over the HSM login method? I am asking because I did not find any online reference suggesting this.
Is there a way of achieving this in a better manner?
回答1:
The answers to all your questions are "hidden" in PKCS#11 v2.20 specificiation.
See Chapter 6.6 for more info on not disposing m_Pkcs11
through out the process life cycle:
An application becomes a "Cryptoki application" by calling the Cryptoki function
C_Initialize
(see Section 11.4) from one of its threads; after this call is made, the application can call other Cryptoki functions. When the application is done using Cryptoki, it calls the Cryptoki functionC_Finalize
(see Section 11.4) and ceases to be a Cryptoki application.
In other words you need to create instance of Pkcs11
class only once and then all your threads can access PKCS#11 functions. I've seen apps that do use single instance of Pkcs11
class and do not dispose it for months. It is a perfectly valid usage.
See chapter 6.7.4 for more info on login state:
In Cryptoki, all sessions that an application has with a token must have the same login/logout status (i.e., for a given application and token, one of the following holds: all sessions are public sessions; all sessions are SO sessions; or all sessions are user sessions). When an application's session logs into a token, all of that application's sessions with that token become logged in, and when an application's session logs out of a token, all of that application’s sessions with that token become logged out.
In other words once you login into one session, you are also logged in all existing sessions, and also in all sessions opened in the future. That is the main reason why you were getting CKU_USER_ALREADY_LOGGED_IN
error. I've seen apps that login into a single session and keep it open for months. BTW you can use Session::GetSessionInfo()
method to check whether your session is already logged in or not.
For a real world example of class similar to yours please take a look at Pkcs11RsaSignature class from Pkcs11Interop.PDF project.
来源:https://stackoverflow.com/questions/44715797/threadsafe-usage-of-pkcs11interop-library-in-c-sharp