首页 > 解决方案 > DotNet Core:如何跨平台客户端证书 TLS 身份验证?

问题描述

我正在尝试开发一个跨平台应用程序(windows/mac os x),该应用程序需要签署 XML 文件并使用 ClientCertificate 身份验证在服务器上发出 Web 请求......

主要限制是我需要在智能卡上使用证书......

我目前正在使用 dotnet core 2.1。

首先,我确实尝试使用 dotnet core X509Store,但在 MacOs 上我无论如何都无法访问 PrivateKey 对象(XML 签名所必需的),然后我放弃了这条线索。

然后我使用 pkcs11interop 和特定于供应商的 dll 来访问智能卡,它适用于 XML 签名(我将 pkcs11interop 调用包装在 RSA 对象中,然后我将此对象用作 SignedXml 中的 SigningKey),但它不适用于 ClientCertificate联系。

通常我使用这种方法连接到服务器(dotnet framework 4.6.1,仅限 windows):

HttpWebRequest req = WebRequest.Create("https://sometlsserver.com/") as HttpWebRequest;
req.ClientCertificates.Add(cert);
HttpWebResponse res = req.GetResponse() as HttpWebResponse;
using (StreamReader sr = new StreamReader(res.GetResponseStream(), new UTF8Encoding(false)))
{
    return sr.ReadToEnd();
}

并生成证书:

var certAttr = session.GetAttributeValue(handle, new List<CKA>
{
    CKA.CKA_VALUE,
};
var cert = new X509Certificate2(certAttr[0].GetValueAsByteArray());

当我尝试连接我的经典方法时,我有几个例外:

System.Net.WebException: The SSL connection could not be established, see inner exception. Authentication failed, see inner exception.
---> System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception.
---> System.Security.Authentication.AuthenticationException: Authentication failed, see inner exception.
---> System.ComponentModel.Win32Exception: Le message reçu était inattendu ou formaté de façon incorrecte
   --- End of inner exception stack trace ---
   at System.Net.Security.SslState.StartSendAuthResetSignal(ProtocolToken message, AsyncProtocolRequest asyncRequest, ExceptionDispatchInfo exception)
   at System.Net.Security.SslState.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.ProcessReceivedBlob(Byte[] buffer, Int32 count, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.StartReadFrame(Byte[] buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.PartialFrameCallback(AsyncProtocolRequest asyncRequest)
--- End of stack trace from previous location where exception was thrown ---
   at System.Net.Security.SslState.ThrowIfExceptional()
   at System.Net.Security.SslState.InternalEndProcessAuthentication(LazyAsyncResult lazyResult)
   at System.Net.Security.SslState.EndProcessAuthentication(IAsyncResult result)
   at System.Net.Security.SslStream.EndAuthenticateAsClient(IAsyncResult asyncResult)
   at System.Net.Security.SslStream.<>c.<AuthenticateAsClientAsync>b__47_1(IAsyncResult iar)
   at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchronization)
--- End of stack trace from previous location where exception was thrown ---
   at System.Net.Http.ConnectHelper.EstablishSslConnectionAsyncCore(Stream stream, SslClientAuthenticationOptions sslOptions, CancellationToken cancellationToken)
   --- End of inner exception stack trace ---
   at System.Net.Http.ConnectHelper.EstablishSslConnectionAsyncCore(Stream stream, SslClientAuthenticationOptions sslOptions, CancellationToken cancellationToken)
   at System.Threading.Tasks.ValueTask`1.get_Result()
   at System.Net.Http.HttpConnectionPool.CreateConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Threading.Tasks.ValueTask`1.get_Result()
   at System.Net.Http.HttpConnectionPool.WaitForCreatedConnectionAsync(ValueTask`1 creationTask)
   at System.Threading.Tasks.ValueTask`1.get_Result()
   at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean doRequestAuth, CancellationToken cancellationToken)
   at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Net.Http.HttpClient.FinishSendAsyncUnbuffered(Task`1 sendTask, HttpRequestMessage request, CancellationTokenSource cts, Boolean disposeCts)
   at System.Net.HttpWebRequest.SendRequest()
   at System.Net.HttpWebRequest.GetResponse()
   --- End of inner exception stack trace ---
   at System.Net.HttpWebRequest.GetResponse()
...

我认为在 TLS 握手期间所需的私钥进行的加密操作丢失了,它只是失败了......

我确实尝试在私钥属性中添加用于 XML 签名的 RSA 对象,但使用 dotnet core 它以 PlatformNotSupportedException 结尾...

cert.PrivateKey = myRSAObject; // simple

我想知道是否可以在不使用第三方库的情况下做到这一点......

注意:法语错误消息“消息意外或格式不正确”的翻译。

谢谢。

(编辑:更好的格式)

标签: .net-coresmartcardpkcs#11hsmpkcs11interop

解决方案


X509Certificate2是仅为 Windows 设计的遗留类,目前没有广泛使用的替代方案或健全的多平台替代品。

当您使用属性值构造X509Certificate2对象时,您只会获得没有私钥的证书。byte[]CKA_VALUE

最接近“.NET 兼容实现”的方法是实现派生自我RSAPkcs11Interop.X509Store项目中所做的类。它将与SignedXML类一起使用,但正如您已经发现的那样,它不适用于大多数需要X509Certificate2具有正确关联PrivateKey属性的对象的 SSL 类。

IMO 你有三个选择:

  1. 切换到适当的 .NET Core 版本并使用@FilipNavara 在他的评论中指出的扩展方法X509Certificate2创建与您的RSA实现关联的对象。这样的对象可能(或者更确切地说可能不会)与 SSL 实现一起使用。CopyWithPrivateKeyX509Certificate2

  2. 使用包含受用户密码保护的私钥的 SSL 客户端证书的 PFX/PKCS#12 文件。在.NET Core 支持的大多数平台上,此类文件可以加载到X509Certificate2对象正确关联的属性中。PrivateKey

  3. 查找或编写不需要X509Certificate2具有关联PrivateKey属性的对象的 SSL 客户端实现。我目前不知道有任何具有这种设计的图书馆。


推荐阅读