在C#中使用PKCS11Interop库的Threadsafe

我正在使用PKCS11Interop在HSM中执行密钥管理操作。 我使用的HSM是Thales PCI Express。 以下是包装在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(); } ///  /// Generates key in the key store ///  ///  ///  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 publicKeyAttributes = CreatePublicKeyTemplate(keyName, ckaId); List privateKeyAttributes = CreatePrivateKeyTemplate(keyName, ckaId); session.GenerateKeyPair(mechanism, publicKeyAttributes, privateKeyAttributes, out publicKeyHandle, out privateKeyHandle); }); } ///  /// Destroys key in the key store ///  ///  ///  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); } }); } ///  /// Encrypts a message using the key in the key store ///  ///  ///  ///  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; } ///  /// Decrypts a key using the key in the key store ///  ///  ///  ///  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 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 foundObjects = null; List objectAttributes = new List(); 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 foundObjects = null; List objectAttributes = new List(); 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 CreatePublicKeyTemplate(string keyName, byte[] ckaId) { List publicKeyAttributes = new List(); 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 CreatePrivateKeyTemplate(string keyName, byte[] ckaId) { List privateKeyAttributes = new List(); 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 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 } 

如果你注意到我正在使用两个对象来应用锁。 对象_syncLockPkcs11用于在m_Pkcs11上实现单例,而_syncLockHSMLogin用于同步登录到HSM。 早些时候,当我没有这些锁时,我曾经从HSM,CKU_USER_ALREADY_LOGGED_IN和CKR_FUNCTION_FAILED获得以下错误。 我根据此链接和6.7.7本文档 会话使用示例中提供的信息实施了此更改 在此处输入图像描述

在我目前的实施中,我没有得到任何这些错误,但想知道这里的专家意见。

我的一些问题是:

是否可以以这种方式使用m_Pkcs11,即不在整个过程生命周期中处理它?

是否可以对HSM登录方法应用锁定? 我问,因为我没有找到任何在线参考建议。

有没有办法以更好的方式实现这一目标?

所有问题的答案都在PKCS#11 v2.20规范中“隐藏”。

有关在整个过程生命周期中不处理m_Pkcs11更多信息,请参见第6.6章:

应用程序通过从其中一个线程调用Cryptoki函数C_Initialize (参见第11.4节)成为“Cryptoki应用程序”; 在进行此调用之后,应用程序可以调用其他Cryptoki函数。 当使用Cryptoki完成应用程序时,它会调用Cryptoki函数C_Finalize (参见第11.4节)并且不再是Cryptoki应用程序。

换句话说,您只需要创建一次Pkcs11类的实例,然后所有线程都可以访问PKCS#11函数。 我见过的应用程序确实使用了Pkcs11类的单个实例,并且几个月都没有处理它。 这是一个完全有效的用法。

有关登录状态的更多信息,请参见第6.7.4章:

在Cryptoki中,应用程序与令牌具有的所有会话必须具有相同的登录/注销状态(即,对于给定的应用程序和令牌,以下之一保持:所有会话都是公共会话;所有会话都是SO会话;或者全部会话是用户会话)。 当应用程序的会话登录到令牌时,该应用程序与该令牌的所有会话都将登录,并且当应用程序的会话退出令牌​​时,该应用程序与该令牌的所有会话都将被注销。

换句话说,一旦您登录到一个会话,您还将登录所有现有会话,以及将来打开的所有会话。 这就是为什么你得到CKU_USER_ALREADY_LOGGED_IN错误的主要原因。 我已经看到登录到单个会话的应用程序并保持打开数月。 顺便说一句,你可以使用Session::GetSessionInfo()方法来检查你的会话是否已经登录。

有关类似于您的类的真实世界示例,请查看Pkcs11Interop.PDF项目中的Pkcs11RsaSignature类。