下午好。
在我的应用程序中,我使用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;
}
}
}