1

TL;DR version (somewhat)

I've got two servers (X and Y) that are trying to communicate, but failing to do so over TLS1.2 (TLS1.1, and TLS1.0 both behave correctly).

I've got a server X that is failing to get a response from localhost.

The call that is throwing an exception is this line of code

HttpResponseMessage response = await httpClient.GetAsync(address, cancel);

where address is https://localhost/XXXXX/Identity/.well-known/jwks and cancel is IsCancellationRequested = false

I noticed that the server certificate being presented for https://localhost/XXXXX/Identity/.well-known/jwks is one that is valid, signed with RSA256, not on a revocation list, etc etc, everything looks good, but the subject is *.testing.corp, which doesn't match the URL I am trying to navigate to. This causes a certificate warning when navigating to https://localhost/XXXXX/Identity/.well-known/jwks from Chrome on server X, but after manually clicking through it I get the results I expect.

So my thought is that if I can override the cert validation of HttpClient.GetAsync, I should be able to make this work, so I add the following code around my client:

var handler = new WebRequestHandler();
httpClient = new HttpClient(handler);
handler.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(dumbvalidationdeletemeishouldnotbeversioned);

public static bool dumbvalidationdeletemeishouldnotbeversioned(object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors err)
{
    return true;
}

The interesting bit is that this never hits, when I step to the GetAsync line of code, a step throws the exception without ever hitting the dummy validation.

What is going on? Why else could this method fail?


Even more context:

EDIT: Related, X is utilizing Identity Server to attempt to authenticate. The failure is occuring when trying to access https://localhost/XXXXX/Identity/.well-known/jwks

I am using IISCrypto to configure the protocols on these machines. (Taking @Oleg's advice in the comments did not change this behavior). I have configured them by clicking the "Best Practices" button and then un-checking TLS1.0 and TLS1.1, so that both servers look like this:

enter image description here

EDIT: Adding some stack trace information

Before I added my dummy handler, this is the thrown exception's stack trace:

System.Net.Http.HttpRequestException: An error occurred while sending the request. ---> System.Net.WebException: The underlying connection was closed: An unexpected error occurred on a send. ---> System.IO.IOException: Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host. ---> System.Net.Sockets.SocketException: An existing connection was forcibly closed by the remote host
   at System.Net.Sockets.NetworkStream.EndRead(IAsyncResult asyncResult)
   --- End of inner exception stack trace ---
   at System.Net.TlsStream.EndWrite(IAsyncResult asyncResult)
   at System.Net.ConnectStream.WriteHeadersCallback(IAsyncResult ar)
   --- End of inner exception stack trace ---
   at System.Net.HttpWebRequest.EndGetResponse(IAsyncResult asyncResult)
   at System.Net.Http.HttpClientHandler.GetResponseCallback(IAsyncResult ar)
   --- End of inner exception stack trace ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at XXXXXXXX.Authentication.Extensions.XXXXXXOpenIDConnectAuthentication.<ReadJWTKeys>d__8.MoveNext() 
   --- End of stack trace from previous location where exception was thrown ---         
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)         
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)         
   at XXXXXXX.Authentication.Extensions.XXXXXXOpenIDConnectAuthentication.<MessageRecieved>d__6.MoveNext()  
   --- End of stack trace from previous location where exception was thrown ---    
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)     
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)     
   at Microsoft.Owin.Security.OpenIdConnect.OpenIdConnectAuthenticationHandler.<AuthenticateCoreAsync>d__1a.MoveNext()

After adding the dummy handler, the exception is completely null <-- see the comments. The exception is exactly the same, but the return statement in my dummy handler is never hit when I debug the process, so the crux of my question changes to "What else is there to check?"

Community
  • 1
  • 1
Matt
  • 1,674
  • 2
  • 16
  • 34
  • 1
    can you post the stacktrace? – raven Apr 25 '16 at 20:31
  • The entire exception is null, so there is no stack trace to post. I'll update the post with a pic to elaborate. – Matt Apr 25 '16 at 20:32
  • The exception is null because you aren't in the main thread. Can you switch back to the main thread? or change your relevant parts of code to make it run synchronously? That may help you to pin point the bug- – raven Apr 25 '16 at 20:44
  • 1
    @Matt It is null because the variable is not yet set. Make one more step and it should get the value. Caught exception can't be null - http://stackoverflow.com/questions/2195764/why-does-c-sharp-allow-you-to-throw-null. – Eugene Podskal Apr 25 '16 at 20:45
  • @RobertoDeLaParra, unfortunately not, that would be a large time investment, so with async we stay. – Matt Apr 25 '16 at 20:47
  • @EugenePodskal great point :facepalm:, the stack trace is identical even with my dummy handler. I'll update the question – Matt Apr 25 '16 at 20:48
  • @Matt Things happen. – Eugene Podskal Apr 25 '16 at 20:48
  • Well, now it seems to be a permission problem, under which folder did you installed the certificate? – raven Apr 25 '16 at 20:54
  • @Matt: The problem is that IISCrypto works not full correctly with Cipher Suites. I recommend you to click on "Defaults" button of IISCrypto to reset Cipher Suites and to use only the top part of IISCrypto to disable only the same protocols, which are disabled above and additionally to disable MD5. After the changes you will have to **reboot** the computer (server). The usage of `gpupdate /force /target:computer` is not enough. You should repeat your tests after the rebooting. It it will work then I could write how you can customize Cipher Suites using `gpedit.msc` instead of IISCrypto. – Oleg Apr 25 '16 at 20:58
  • @Oleg I'll give this a shot – Matt Apr 25 '16 at 21:13
  • @Oleg, no luck. I'm seeing the same behavior as before. – Matt Apr 25 '16 at 21:27
  • @Matt: You still have the same error "An error occurred while sending the request." and `https://unauthenticated/endpoint` or some another one? Which Windows Servers you use (2012, 2008, ...)? – Oleg Apr 25 '16 at 21:30
  • @Oleg, it is identical to what is currently in the post, yes. – Matt Apr 25 '16 at 21:39
  • @Matt: Which Windows Servers you use (2012, 2008, ...)? On which server use start the program and to which server you establish the connection? – Oleg Apr 25 '16 at 21:40
  • @Oleg you are correct about the URL. Both servers are on Windows Server 2008 R2 Standard Service Pack 1. Communication is happening from X to localhost. I'm going to add this to the post, but the setup is also using IdentityServer – Matt Apr 25 '16 at 21:41
  • @Matt: Another question: are both server connected over the LAN? Why you disabled TLS 1.1 and TLS 1.0? Do you have running IIS on the server which is accessible over Internet? Or you have common policy in the company: all servers and all clients have TLS 1.1 and TLS 1.0 disabled? – Oleg Apr 25 '16 at 21:45
  • I do not know how they are connected, but with my latest edit I'm not so sure they are even attempting to communicate.... We are attempting to establish our product as protocol agnostic (and set up an environment where only TLS1.2 is enabled), but that is clearly not the case when TLS 1.2 exhibits this behavior. – Matt Apr 25 '16 at 21:48
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/110189/discussion-between-matt-and-oleg). – Matt Apr 25 '16 at 21:50

1 Answers1

3

One should add

ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

(from System.Net namespace) before call of

await httpClient.GetAsync(address, cancel);

to force the usage of correct protocol. See the blog with detailed experiments with HttpClient and ServicePointManager.SecurityProtocol.

Oleg
  • 220,925
  • 34
  • 403
  • 798