【问题标题】:Threadsafe usage of PKCS11Interop library in C# [closed]C#中PKCS11Interop库的线程安全使用[关闭]
【发布时间】:2017-06-23 07:43:09
【问题描述】:

我正在使用 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();
        }

        /// <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
    }

如果你注意到我正在使用两个对象来应用锁。对象 _syncLockPkcs11 用于在 m_Pkcs11 上实现单例,而 _syncLockHSMLogin 用于将 Login 同步到 HSM 。早些时候,当我没有安装这些锁时,我曾经从 HSM、CKU_USER_ALREADY_LOGGED_IN 和 CKR_FUNCTION_FAILED 收到以下错误。我根据linkdocument6.7.7 会话使用示例 部分中提供的信息实施了这些更改,即

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

我的一些问题是:

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

是否可以对 HSM 登录方法应用锁定?我之所以这么问,是因为我没有找到任何暗示这一点的在线参考资料。

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

【问题讨论】:

  • 我投票结束这个问题,因为它属于codereview.stackexchange.com
  • @SirRufo 虽然看起来这个问题似乎更适合 codereviewexchange,但我实际上想知道我对 PKCS11Interop usgae 的理​​解是否正确。如果我将它添加到 codereviewexchange 我可能无法得到正确的响应,codereviewexchange 甚至没有 #pkcs11 的标签。
  • 在我目前的实施中,我没有遇到任何这些错误,但想在这里了解专家对我的实施的意见。如果您在我的实现中发现任何问题或改进范围,请告诉我。 明确要求代码审查
  • @SirRufo 感谢您通知我这一点,我已经编辑了这个问题。阿什什
  • *Sigh* 一定很喜欢这样的用户,他们投票通过代码示例来关闭完全有效的问题,而不是试图回答它。

标签: c# pkcs#11 hsm pkcs11interop


【解决方案1】:

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

请参阅第 6.6 章了解有关在整个流程生命周期中不释放 m_Pkcs11 的更多信息:

通过调用 Cryptoki 函数C_Initialize(参见第 11.4 节)来自它的一个 线程;进行此调用后,应用程序可以调用其他 Cryptoki 函数。当应用程序使用 Cryptoki 完成时,它 调用 Cryptoki 函数C_Finalize(参见第 11.4 节)并停止 成为 Cryptoki 应用程序。

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

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

在 Cryptoki 中,应用程序具有令牌的所有会话都必须 具有相同的登录/注销状态(即,对于给定的应用程序和 令牌,以下之一持有:所有会话都是公共会话; 所有会话都是 SO 会话;或所有会话都是用户会话)。什么时候 应用程序的会话登录到令牌中,该应用程序的所有 具有该令牌的会话已登录,并且当应用程序的 session 注销一个令牌,该应用程序的所有会话与 该令牌已注销。

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-08-12
    • 1970-01-01
    • 1970-01-01
    • 2010-11-22
    • 1970-01-01
    • 2014-07-20
    • 1970-01-01
    • 2019-12-04
    相关资源
    最近更新 更多