C# 当我的应用程序的多个进程同时运行时,Windows会随机请求智能卡/令牌PIN

C# 当我的应用程序的多个进程同时运行时,Windows会随机请求智能卡/令牌PIN,c#,authentication,cryptography,smartcard,x509certificate2,C#,Authentication,Cryptography,Smartcard,X509certificate2,下午好 在我的应用程序中,我使用System.Net.WebClient与需要用户证书进行SSL身份验证的网站服务器通信, 可存储在外部智能卡/令牌设备中。我使用Windows CSP访问经过验证的X509Certificate2实例 我可以访问存储在智能卡/读卡器或令牌中的证书,并且在使用时不会出现问题 只是应用程序运行的单个Windows进程。该进程可以运行超过个小时 一小时,通过System.Net.WebClient发送多个Http请求(附带证书) 问题在于,当(同一应用程序/解决方案的

下午好

在我的应用程序中,我使用System.Net.WebClient与需要用户证书进行SSL身份验证的网站服务器通信, 可存储在外部智能卡/令牌设备中。我使用Windows CSP访问经过验证的X509Certificate2实例

我可以访问存储在智能卡/读卡器或令牌中的证书,并且在使用时不会出现问题 只是应用程序运行的单个Windows进程。该进程可以运行超过个小时 一小时,通过System.Net.WebClient发送多个Http请求(附带证书)

问题在于,当(同一应用程序/解决方案的)多个Windows进程同时运行时, 使用来自智能卡/令牌的相同证书,经过一段时间(几分钟)后,应用程序启动 在Windows默认PIN UI中请求用户PIN(当只有一个进程在运行时,这种情况永远不会发生)

即使我在UI中手动通知正确的PIN,System.Net.WebClient也无法执行其操作,并且 需要启动新进程才能使智能卡/令牌证书再次工作

下面我将解释并提供我在解决方案中使用的示例代码:

首先,我从Windows密钥库中检索System.Security.Cryptography.X509Certificates.X509Certificate2,它与智能卡/令牌相关/附加:

private X509Certificate2 GetClientCertificate()
    {
        X509Store KeyStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
        KeyStore.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
        var clientCert = GetClientCertFromCollection(KeyStore.Certificates);
    }
从上面的clientCert实例来看,它只是公钥,因为我仍然无法从它访问私钥。 为了访问智能卡/令牌中的私钥,我与设备/硬件进行了“握手” 使用Microsoft/Windows加密服务提供商(CSP)附加。代码如下:

private void WithHardwareDeviceHandshake(X509Certificate2 certificate, SecureString securePassword)
    {
        if (certificate.HasPrivateKey == false)
            throw new CertificateException("The private key  Não foi encontrada a chave privada associada ao Certificado Digital");

        var cspAsymmetricAlgorithm = certificate.PrivateKey as ICspAsymmetricAlgorithm;
        if (cspAsymmetricAlgorithm == null)
            throw new CertificateException($"Not an instance of {nameof(ICspAsymmetricAlgorithm)}");

        var keyContainer = cspAsymmetricAlgorithm.CspKeyContainerInfo;
        if (keyContainer == null)
            throw new CertificateException($"The {nameof(CspKeyContainerInfo)} is null. Its not possible to access the private key.");

        if (keyContainer.HardwareDevice == false || keyContainer.Removable == false)
            throw new CertificateException($"The Certificate Serial Number {certificate.SerialNumber} is not associated with an Smartcard/Token device.");

        CspParameters cspParameters = new CspParameters(
            keyContainer.ProviderType,
            keyContainer.ProviderName,
            keyContainer.KeyContainerName,
            new System.Security.AccessControl.CryptoKeySecurity(),
            securePassword);

        RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider(cspParameters);

        if (certificate.PublicKey.Key.ToXmlString(false) != rsaProvider.ToXmlString(false))
            throw new CertificateException($"The Certificate with the Serial Number {certificate.SerialNumber} accessed in the Windows Keystore was not found on the Smartcard/Token.");
    }
在上述握手(如果执行)后,我可以从智能卡/令牌设备自由访问私钥,因为我为其提供了PIN。 在此之后,我使用所有证书颁发机构树/链构建了一个CertificateCollection:

public X509Certificate2Collection BuildX509CertificateChainCollection(X509Certificate2 cert)
    {
        if (cert == null)
            return null;

        X509Certificate2Collection certificationChainCollec = new X509Certificate2Collection();


        X509Chain chain = new X509Chain();
        chain.Build(cert);
        foreach (X509ChainElement chainElementItem in chain.ChainElements)
            certificationChainCollec.Add(chainElementItem.Certificate);

        return certificationChainCollec;
    }
每当System.Net.WebClient需要执行Web请求时,都会向其提供上述certificationChainCollec。 为了做到这一点,我专门研究了这门课:

public class HttpWebClientEngine : System.Net.WebClient
{           
    public HttpWebClientEngine(X509CertificateCollection collection) : base()
    {
        this.CertCollection = collection;
    }

    private X509CertificateCollection CertCollection { get; set; }

    protected override WebRequest GetWebRequest(Uri address)
    {
        HttpWebRequest request = (HttpWebRequest)base.GetWebRequest(address);
        DecorateRequestWithCertificate(request);
        return request;
    }

    private void DecorateRequestWithCertificate(HttpWebRequest request)
    {
        if (CertCollection?.Count > 0)
            request.ClientCertificates.AddRange(CertCollection);
    }

}
然后我简单地使用它来执行HTTP GET/POST请求:

public class HttpWebClient : IDisposable
{
    public X509CertificateCollection CertCollection { get; set; }

    private WebClient CreateWebClient()
    {
        this.ValidateKeepAliveHeader();
        var webClient = new HttpWebClientEngine(this.CertCollection);
        return webClient;
    }

    public string PostMethod(Uri uri, Payload contentData)
    {
        using (var client = this.CreateWebClient())
        {
            var webResponse = String.Empty;
            try
            {
                var data = contentData.Data;
                webResponse = client.UploadString(uri, data ?? String.Empty);
            }
            catch (System.Net.WebException ex)
            {
                webResponse = new StreamReader(((WebException)ex).Response?.GetResponseStream())?.ReadToEnd() ?? string.Empty;
            }

            return webResponse;
        }                           
    }

    public string GetMethod(Uri uri)
    {
        using (var client = this.CreateWebClient())
        {
            var webResponse = String.Empty;
            try
            {
                webResponse = client.DownloadString(uri);
            }
            catch (System.Net.WebException ex)
            {
                webResponse = new StreamReader(((WebException)ex).Response?.GetResponseStream())?.ReadToEnd() ?? string.Empty;
            }
            return webResponse;
        }
    }

}