DotNet Core:如何跨平台客户端证书 TLS 身份验证?

Posted

技术标签:

【中文标题】DotNet Core:如何跨平台客户端证书 TLS 身份验证?【英文标题】:DotNet Core: How to cross-platform Client Certificate TLS Authentication? 【发布时间】:2019-05-09 12:24:15 【问题描述】:

我正在尝试开发一个跨平台应用程序 (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 only):

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

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

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

谢谢。

(编辑:更好的格式)

【问题讨论】:

PrivateKey 属性设置器已替换为支持 RSA、DSA 和 ECC 的 CopyWithPrivateKey 扩展方法(例如,docs.microsoft.com/en-us/dotnet/api/…)。所有这些都适用于所有平台上的 .NET Core。 @FilipNavara 感谢您指出该方法!如果 SSL 实现将使用RSA,那可能会改变游戏规则。 谢谢,我错过了那个方法,我会尽快尝试。 很遗憾,它没有用。 CopyWithPrivateKey 期望私钥的指数和模数,并且无法从智能卡中提取私人指数...我目前正在调查 bouncycastle,似乎很有希望? 【参考方案1】:

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

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

最接近“.NET 兼容实现”的方法是实现从RSA 派生的类,就像我在Pkcs11Interop.X509Store 项目中所做的那样。它适用于 SignedXML 类,但正如您已经发现的那样,它不适用于大多数需要 X509Certificate2 对象和正确关联的 PrivateKey 属性的 SSL 类。

IMO 你有三个选择:

    切换到适当的 .NET Core 版本并使用 @FilipNavara 在他的评论中指出的使用 CopyWithPrivateKey extension method 创建与您的 RSA 实现关联的 X509Certificate2 对象。这样的X509Certificate2 对象可能(或者更确切地说可能不)适用于 SSL 实现。

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

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

【讨论】:

以上是关于DotNet Core:如何跨平台客户端证书 TLS 身份验证?的主要内容,如果未能解决你的问题,请参考以下文章

.NET跨平台之旅:探秘 dotnet run 如何运行 .NET Core 应用程序

温故知新,.Net Core遇见WinForms客户端窗体框架,在DotNet Core大一统基础上老树发芽...

dotnet跨平台微软昨天宣布正式发布.NET Core RC2和.NET Core SDK Preview 1,还有Entity Framework Core RC2

使用 dotnet core 和 Azure PaaS服务进行devOps开发(Web API 实例)

net core 微服务需要哪些框架

应用工具 .NET Portability Analyzer 分析迁移dotnet core