runtime: SocketsHttpHandler rejects certificate with RemoteCertificateNameMismatch

Reported by @ydekova in https://github.com/dotnet/corefx/issues/29942#issuecomment-397006087

The following code throws on .NET Core 2.1, and does not throw on .NET Core 2.0:

using System;
using System.Net.Http;
using System.Threading.Tasks;

namespace console2
{
    class Program
    {
        static async Task Main(string[] args)
        {
            using (var httpClient = new HttpClient())
            {
                var response = await httpClient.GetAsync("https://outlook.office.com");
                System.Console.WriteLine(response.StatusCode);
            }
        }
    }
}
Unhandled Exception: System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception. ---> System.Security.Authentication.AuthenticationException: The remote certificate is invalid according to the validation procedure.
   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.StartReceiveBlob(Byte[] buffer, AsyncProtocolRequest asyncRequest)
   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.FinishSendAsyncBuffered(Task`1 sendTask, HttpRequestMessage request, CancellationTokenSource cts, Boolean disposeCts)
   at console2.Program.Main(String[] args) in /tmp/console2/Program.cs:line 13
   at console2.Program.<Main>(String[] args)

@ydekova’s comment https://github.com/dotnet/corefx/issues/29942#issuecomment-397200251:

The SslPolicyError (errors param in the above code) is RemoteCertificateNameMismatch

When using the curlhandler on 2.1 (DOTNET_SYSTEM_NET_HTTP_USESOCKETSHTTPHANDLER=0), the code works.

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 1
  • Comments: 33 (21 by maintainers)

Most upvoted comments

We should consider fixing this for servicing release/2.1.x as well.

cc: @karelz

I have web server with wild card certificate ( *.mdrinc.com) running on port 1060. When I try to reach server using HttpClient I get “The remote certificate is invalid according to the validation procedure” error . With HttpClient handler with ServerCertificateCustomValidationCallback I see “SslPolicyErrors.RemoteCertificateNameMismatch”.

Same program works with AppContext.SetSwitch(“System.Net.Http.UseSocketsHttpHandler”, false);

My .NET Environment is

dotnet --info .NET Core SDK (reflecting any global.json):

 Version:   2.2.100
 Commit:    b9f2fa0ca8

Runtime Environment:

 OS Name:     ubuntu
 OS Version:  18.04
 OS Platform: Linux
 RID:         ubuntu.18.04-x64
 Base Path:   /usr/share/dotnet/sdk/2.2.100/

Host (useful for support):

  Version: 2.2.0
  Commit:  1249f08fed

.NET Core SDKs installed: 2.2.100 [/usr/share/dotnet/sdk]

.NET Core runtimes installed:

  Microsoft.AspNetCore.All 2.2.0 [/usr/share/dotnet/shared/Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.App 2.2.0 [/usr/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 2.2.0 [/usr/share/dotnet/shared/Microsoft.NETCore.App]_

Example: my server returns following server certificate

[Subject]

  E=joe@mdrinc.com, CN=*.mdrinc.com, OU=Dev, O=mdrinc, L=Bangalore, S=Karnataka, C=IN
  Simple Name: *.mdrinc.com
  Email Name: joe@mdrinc.com
  DNS Name: *.mdrinc.com

[Issuer]

  E=a@b.com, CN=CA.HCPF.COM, OU=CA, O=mdrinc, L=Bangalore, S=Karnataka, C=IN
  Simple Name: CA.HCPF.COM
  Email Name: a@b.com
  DNS Name: CA.HCPF.COM

[Serial Number] 01

[Not Before] 04/12/18 2:17:36 PM

[Not After] 03/12/20 2:17:36 PM

https://gist.github.com/madhub/39c980ff010ba3888e54e8f1527ced6b#file-ssltest-cs

Sample Program

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Text;

class Program
{
    public class MyHttpClientHandler : HttpClientHandler
    {
        public MyHttpClientHandler()
        {
            this.ServerCertificateCustomValidationCallback = delegate (HttpRequestMessage message,
                X509Certificate2 x509Cer2,
                X509Chain chain,
                SslPolicyErrors errors)
            {
                bool validationStatus = true;
                if ((errors & SslPolicyErrors.RemoteCertificateChainErrors) != 0)
                {
                    Console.WriteLine("SslPolicyErrors.RemoteCertificateChainErrors");
                    validationStatus = false;
                }

                if ((errors & SslPolicyErrors.RemoteCertificateNameMismatch) != 0)
                {
                    Console.WriteLine("SslPolicyErrors.RemoteCertificateNameMismatch");
                    validationStatus = false;
                }
                else if ((errors & SslPolicyErrors.None) != 0)
                {
                    Console.WriteLine("SslPolicyErrors.None");
                    validationStatus = true;
                }

                Console.WriteLine(x509Cer2.ToString(true));

                return validationStatus;
            };
        }

    }
    // this works
    public static void WithAppContextSwitch(string hostUrl)
    {
        AppContext.SetSwitch("System.Net.Http.UseSocketsHttpHandler", false);
        try
        {
            using (var httpClient = new HttpClient())
            {
                var response = httpClient.GetAsync(hostUrl).GetAwaiter().GetResult();
                System.Console.WriteLine(response.StatusCode);
            }
        }
        catch (Exception exp)
        {

            Console.WriteLine("Exception " + exp);
        }
    }
    // throws RemoteCertificateNameMismatch exception 
    public static void WithOutAppContextSwitch(string hostUrl)
    {

        try
        {
            var httpHandler = new MyHttpClientHandler();
            using (var httpClient = new HttpClient(httpHandler))
            {
                var response = httpClient.GetAsync(hostUrl).GetAwaiter().GetResult();
                System.Console.WriteLine(response.StatusCode);
            }
        }
        catch (Exception exp)
        {

            Console.WriteLine("Exception " + exp);
        }
    }

    static void Main(string[] args)
    {

        string hostUrl = "https://msrv4.mdrinc.com:1060";

        // this works
        WithAppContextSwitch(hostUrl);

        // throws RemoteCertificateNameMismatch exception 
        WithOutAppContextSwitch(hostUrl);
    }
}

It’s not really “relax” it’s “match everyone else”. ~4 line change in crypto native.