首页 > 解决方案 > 在多个 CMS 签名中缓存 PIN

问题描述

好吧,我在这里找到的大多数问题/答案都是关于不缓存智能卡 PIN 的,这与我正在寻找的情况相反。

我们有一个对多个哈希进行签名的控制台应用程序。为此,我们使用Pkcs.CmsSigner因为我们需要在服务器端验证签名的哈希。

通常,智能卡的 PIN 应该在每个进程的 CSP 中自动缓存,并且在 Windows 7 中会这样做,但如果我们在 W10 中运行我们的代码,则不会。我们还支持 CNG 和非 CNG 证书。

我们用来签名的方法如下:

public string SignX509(string data, bool chkSignature, string timestampServer, X509Certificate2 selectedCertificate)
{

    CmsSigner oSigner = null;
    SignedCms oSignedData = null;
    string hashText = String.Empty;

    try
    {
        if (chkSignature)
        {
            oSigner = new CmsSigner();

            oSigner.Certificate = selectedCertificate;

            byte[] arrDataHashed = HashSHA1(data);

            // hash the text to sign
            ContentInfo info = new ContentInfo(arrDataHashed);

            // put the hashed data into the signedData object
            oSignedData = new SignedCms(info);

            if (string.IsNullOrEmpty(timestampServer)) {
                oSigner.SignedAttributes.Add(new Pkcs9SigningTime(DateTime.Now));
            }
            else {
                TimeStampToken tsToken = GetTSAToken(arrDataHashed, timestampServer);

                AsnEncodedData timeData = new Pkcs9AttributeObject(Org.BouncyCastle.Asn1.Pkcs.PkcsObjectIdentifiers.IdAASigningCertificate.Id, tsToken.GetEncoded());
                oSigner.UnsignedAttributes.Add(timeData);
                oSigner.SignedAttributes.Add(new Pkcs9SigningTime(tsToken.TimeStampInfo.GenTime.ToLocalTime()));
            }

            // sign the data
            oSignedData.ComputeSignature(oSigner, false);

            hashText = Convert.ToBase64String(oSignedData.Encode());
            
        }
        else 
        {
            // just clean the hidden hash text
            hashText = String.Empty;
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine("ERRNO [" + ex.Message + " ]");
        return null;
    }

    return hashText;
}

到目前为止,我们已经尝试过:

  1. 使用 RSACryptoServiceProvider 将密钥显式保存在 CSP 中
RSACryptoServiceProvider key = (RSACryptoServiceProvider)cmsSigner.Certificate.PrivateKey;
key.PersistKeyInCsp = true; 

如果我们使用SignHash方法,这是可行的,但正如我之前所说,我们需要在服务器端验证签名数据,并且我们无权访问证书,因此我们需要一个 PKCS 信封。如果我设置此布尔值并使用 CMS 代码签名,则行为是相同的。

  1. 以编程方式设置 PIN

另一种尝试是通过 CryptoContext 以编程方式设置 PIN,基于此答案

private void SetPinForPrivateKey(X509Certificate2 certificate, string pin) {

    if (certificate == null) throw new ArgumentNullException("certificate");
    var key = (RSACryptoServiceProvider)certificate.PrivateKey;

    var providerHandle = IntPtr.Zero;
    var pinBuffer = System.Text.Encoding.ASCII.GetBytes(pin);

    // provider handle is implicitly released when the certificate handle is released.
    SafeNativeMethods.Execute(() => SafeNativeMethods.CryptAcquireContext(ref providerHandle,
                                    key.CspKeyContainerInfo.KeyContainerName,
                                    key.CspKeyContainerInfo.ProviderName,
                                    key.CspKeyContainerInfo.ProviderType,
                                    SafeNativeMethods.CryptContextFlags.Silent));

    SafeNativeMethods.Execute(() => SafeNativeMethods.CryptSetProvParam(providerHandle,
                                    SafeNativeMethods.CryptParameter.KeyExchangePin,
                                    pinBuffer, 0));
    SafeNativeMethods.Execute(() => SafeNativeMethods.CertSetCertificateContextProperty(
                                    certificate.Handle,
                                    SafeNativeMethods.CertificateProperty.CryptoProviderHandle,
                                    0, providerHandle));
}

使用这种方法,我可以通过以编程方式设置 PIN 来禁用 PIN 提示。这里的问题是我必须在第一次读取 PIN 时才能在后续签名中设置它。

我尝试使用CryptoGetProvParam与 dwParam PP_ADMIN_PIN 和 PP_KEYEXCHANGE_PIN 从提示中读取 PIN,但没有运气。我的两个猜测是:

  1. 我没有在正确的时间或方式阅读
  2. CMS 在内部使用不同的处理程序

问题一:

有什么方法可以读取 Windows 提示符中设置的 PIN 码吗?

W10提示

问题2:

如果无法读取 PIN,是否有其他方法可以强制 PIN 缓存?

标签: c#cryptographysmartcard

解决方案


直到现在才意识到这个问题仍然没有答案,尽管我们设法绕过了整个“从 Windows 提示符读取 PIN”问题。

这种方法没有回答我的第一个问题,但我将回答第二个问题。

智能卡 CSP 提供程序中存在一个错误,它禁用了所有请求的 PIN 缓存,SignHash即使它们是在同一进程中发出的。

智能卡提供商有一个 SDK,它公开了一些智能卡操作,是验证智能卡 PIN 的操作之一。

我们最终做的是创建一个简单的 WPF 窗口,该窗口请求用户的 PIN 并使用 SDK 来验证 PIN。如果正确,我们使用我在原始问题中发布的方法来强制 PIN 缓存:

另一种尝试是通过 CryptoContext 以编程方式设置 PIN,基于此答案:

private void SetPinForPrivateKey(X509Certificate2 certificate, string pin) {

    if (certificate == null) throw new ArgumentNullException("certificate");
    var key = (RSACryptoServiceProvider)certificate.PrivateKey;

    var providerHandle = IntPtr.Zero;
    var pinBuffer = System.Text.Encoding.ASCII.GetBytes(pin);

    // provider handle is implicitly released when the certificate handle is released.
    SafeNativeMethods.Execute(() => SafeNativeMethods.CryptAcquireContext(ref providerHandle,
                                    key.CspKeyContainerInfo.KeyContainerName,
                                    key.CspKeyContainerInfo.ProviderName,
                                    key.CspKeyContainerInfo.ProviderType,
                                    SafeNativeMethods.CryptContextFlags.Silent));

    SafeNativeMethods.Execute(() => SafeNativeMethods.CryptSetProvParam(providerHandle,
                                    SafeNativeMethods.CryptParameter.KeyExchangePin,
                                    pinBuffer, 0));
    SafeNativeMethods.Execute(() => SafeNativeMethods.CertSetCertificateContextProperty(
                                    certificate.Handle,
                                    SafeNativeMethods.CertificateProperty.CryptoProviderHandle,
                                    0, providerHandle));
}

有了这个,我们可以在签署多个哈希时只请求一次 PIN,直到智能卡提供商修复他们这边的错误。


推荐阅读