weixin_39713814
weixin_39713814
2021-01-11 11:41

Linux - SslStream.AuthenticateAsClient is not presenting client certificates to server

This issue exists on Linux only

When establishing a connection to a TCP server where a client certificate is required, the client certificate does not get passed to the server side. The connection then gets aborted by the server, resulting in an exception as below.

Using NetMon, we can observe during that handshake that the server successfully sends its certificate to the client, but the client never presents the client certificate to the server. At this point, the server will send an RST and the connection is killed.

I've isolated the repro to using just a TcpClient and TcpServer + SslStream - see https://gist.github.com/iamjasonp/27cd1ebce287f8b51d0e for the rudimentary repro.

Platforms tested - .NET Desktop - :white_check_mark: - .NET Core - Windows: :white_check_mark: - .NET Core - Linux: :x:

First chance exception caught:


'System.Security.Authentication.AuthenticationException' in System.dll
Additional information: The remote certificate is invalid according to the validation procedure.

Server call stack:


System.dll!System.Net.Security.SslState.ForceAuthentication(bool receiveFirst, byte[] buffer, System.Net.AsyncProtocolRequest asyncRequest)
System.dll!System.Net.Security.SslState.ProcessAuthentication(System.Net.LazyAsyncResult lazyResult)
System.dll!System.Net.Security.SslStream.AuthenticateAsServer(System.Security.Cryptography.X509Certificates.X509Certificate serverCertificate, bool clientCertificateRequired, System.Security.Authentication.SslProtocols enabledSslProtocols, bool checkCertificateRevocation)
TcpRepro-Server.exe!Examples.System.Net.SslTcpServer.ProcessClient(System.Net.Sockets.TcpClient client) 
TcpRepro-Server.exe!Examples.System.Net.SslTcpServer.RunServer() 
TcpRepro-Server.exe!Examples.System.Net.SslTcpServer.Main(string[] args)

This also looks to be the root cause of the issue in https://github.com/dotnet/wcf/issues/619#issuecomment-173177433

该提问来源于开源项目:dotnet/runtime

  • 点赞
  • 写回答
  • 关注问题
  • 收藏
  • 复制链接分享
  • 邀请回答

15条回答

  • weixin_40004081 weixin_40004081 3月前

    Why doesn't SslStream throw a descriptive exception to the client in this scenario? The consumer passed a certificate which didn't have a private key. I understand not throwing immediately as the server might not ask for it and that would break existing code, but the point where the server requests the client certificate and the client can't prove it has the private key, when the server aborts the handshake I would expect something to be mentioned about this problem in the exception message.

    点赞 评论 复制链接分享
  • weixin_39618275 weixin_39618275 3月前

    It did have a private key.

    点赞 评论 复制链接分享
  • weixin_39754603 weixin_39754603 3月前

    Why doesn't SslStream throw a descriptive exception to the client in this scenario?

    I think that the design of SslStream was to allow passing the entirety of store.Certificates from the LM\My or CU\My store. It does some matchy logic to try to find more private keys, then ignores anything that was public-only (and, sometimes, "this cert doesn't chain to something on the server's published trust list") and it doesn't understand "I don't have any client certs" to be an error.

    And now, it's "because it hasn't thrown an exception for this ever, so it'd be a breaking change"

    点赞 评论 复制链接分享
  • weixin_39681644 weixin_39681644 3月前

    Which version of the packages are you using? This should have been fixed with https://github.com/dotnet/corefx/pull/5551

    点赞 评论 复制链接分享
  • weixin_39681644 weixin_39681644 3月前

    Sorry, I meant https://github.com/dotnet/corefx/pull/5635

    点赞 评论 复制链接分享
  • weixin_39713814 weixin_39713814 3月前

    We were using

    
      "dependencies": {
        "System.Net.Primitives": "4.0.11-rc2-23712",
        "System.Net.Sockets": "4.0.1-rc2-23712",
        "System.Net.Security": "4.0.0-rc2-23712",
    

    I'll double check with newer packages and see what happens

    点赞 评论 复制链接分享
  • weixin_39681644 weixin_39681644 3月前

    Thanks, . I'm going to close this on the assumption that it is the problem. If the issue persists after updating to today's packages, please re-open.

    点赞 评论 复制链接分享
  • weixin_39713814 weixin_39713814 3月前

    Hey - was trying this with packages from rc3-23725, and unfortunately the issue is still happening for me on Linux.

    On Windows (.NET Desktop and .NET Core), this scenario continues to work.

    点赞 评论 复制链接分享
  • weixin_39713814 weixin_39713814 3月前

    cc ; I think what you're doing might be relevant to the conversation

    点赞 评论 复制链接分享
  • weixin_39754603 weixin_39754603 3月前

    Looks like there were a couple different problems: 1) Due to underlying API differences between SChannel and OpenSSL the Unix builds never got into a state where they thought they were supposed to bind the client cert handles. 2) The server side didn't like receiving certificates with error-containing X509Chains. The chain got built twice, the first time decided if we aborted the handshake; the second was the one that fed into the user callback. 3) The test that would have told us all this was disabled due to recurring intermittent timeouts on Windows 4) Previously the tests were only run on Windows anyways.

    Playing around with some more things and ensuring that there's adequate test coverage. I expect to issue a PR today.

    点赞 评论 复制链接分享
  • weixin_39844267 weixin_39844267 3月前

    : Not sure if this is related to this specific issue, but it does have some similarity with the description. On Windows 7: stream.AuthenticateAsClientAsync(host, certificates, SslProtocols.Tls, true) properly authenticates the client with the server and moves on to send/read. On Ubuntu 16.04 same call kills Kestrel process with no exception. systemd[1]: kestrel-xxx.service: Main process exited, code=killed, status=9/KILL systemd[1]: kestrel-xxx.service: Unit entered failed state. systemd[1]: kestrel-xxx.service: Failed with result 'signal'.

    Windows 7: dotnet --version 1.0.0-preview3-004056 Ubuntu 16.04: dotnet --version: 1.0.0-preview2-1-003177

    点赞 评论 复制链接分享
  • weixin_39618275 weixin_39618275 3月前

    I'm running into this issue again under Ubuntu 16.04. dotnet --version = 2.0.0

    I'm trying to connect to a Dovecot IMAP server configured to require client SSL certificates. Dovecot logs show that no certificate is sent when the client is running on Linux, but it is sent (and authentication succeeds) when running on Windows 7.

    Minimal repro client code:

    var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
    socket.Connect("192.168.x.x", 993);
    
    var ssl = new SslStream(new NetworkStream(socket, true), false, (sender, certificate, chain, errors) => true);
    var clientCert = X509Certificate.CreateFromCertFile("pathtocertfile");
    ssl.AuthenticateAsClient("myserver", new X509CertificateCollection(new [] { clientCert }),
        SslProtocols.Tls12, false);
    
    点赞 评论 复制链接分享
  • weixin_39618275 weixin_39618275 3月前

    Also, SslStream.IsMutuallyAuthenticated is false after running the above code on Linux, but true on Windows.

    点赞 评论 复制链接分享
  • weixin_39754603 weixin_39754603 3月前

    What is the value of clientCert.HasPrivateKey? If it's false then there's logic to search the CurrentUser\My and (on non-Linux) LocalMachine\My stores to find a matching cert which has a private key; which you may have installed in Windows but not Linux. If it's true (e.g. the cert file is a PFX with a private key) then it should work since the private key is already known.

    点赞 评论 复制链接分享
  • weixin_39618275 weixin_39618275 3月前

    you've just helped me find the workaround for this! Thanks very much.

    HasPrivateKey is a property on X509Certificate2, while I had a X509Certificate. If I add the line:

    clientCert = new X509Certificate2(clientCert);
    

    then authentication succeeds and IsMutuallyAuthenticated = true. (And yes, HasPrivateKey is true.)

    This seems like some bizarre behaviour - I'm not sure why it would fail with X509Certificate, but work with X509Certificate2! Once known, though, it's easy to work around.

    点赞 评论 复制链接分享