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

时间:2018-01-25 19:46:08

标签: c# authentication cryptography smartcard x509certificate2

下午好。

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

我可以在没有任何问题的情况下访问存储在智能卡/阅读器或令牌中的证书  只运行一个应用程序的Windows进程。流程可以运行超过 一小时,通过System.Net.WebClient发送许多Http请求(附带证书)

问题是,当多个Windows进程(同一应用程序/解决方案)同时运行时, 在一些经过的时间(几分钟)后,应用程序启动时使用智能卡/令牌中的相同证书 在Windows默认PIN UI中请求用户PIN(绝不会在单个进程运行时发生)。

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

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

首先,我从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;
    }

每次需要执行Web请求时,上面的certificationChainCollec都会提供给System.Net.WebClient。 为了做到这一点,我专门研究课程:

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

}

0 个答案:

没有答案