C# 当我的应用程序的多个进程同时运行时,Windows会随机请求智能卡/令牌PIN
下午好 在我的应用程序中,我使用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,它与智能卡/令牌相关/附加: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请求(附带证书) 问题在于,当(同一应用程序/解决方案的
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;
}
}
}