0

I have an Windows Forms application which targets .NET 4.0 and runs on x64 Windows Server 2012 R2 server with .NET 4.0 as the latest version. In order to send requests to a server (which is used nationwide), you need to append a session token (acquired from the server) in the header of your request. The token is acquired from the server as per official documentation:

bool ServerCertificateBypass(object sender,
                             X509Certificate certificate,
                             X509Chain chain,
                             SslPolicyErrors sslPolicyErrors)
{
    return true;
} 

public string AcquireToken (X509Certificate userCertificate, 
              string password, string userName) 
{
    // configure general http options
    ServicePointManager.ServerCertificateValidationCallback = ServerCertificateBypass;
    ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 |
                        SecurityProtocolType.Tls; // default in .NET
    // create https request
    var url = String.Format("https://serveraddress/validator?username={0}", userName);
    var request = (HttpWebRequest)WebRequest.Create(new Uri(url));
    // configure web request
    request.Accept = "*/*";
    request.KeepAlive = true;
    request.AllowAutoRedirect = false;
    request.PreAuthenticate = true;
    // set Internet Explorer proxy
    request.Proxy = ProxyHelper.GetSystemWebProxy();
    // add user certificate
    request.ClientCertificates.Add(userCertificate); // userCertificate is a X509Certificate object
    // add server credentials
    var credentials = new CredentialCache();
    credentials.Add(uri, "Basic", new NetworkCredential(userName.password));
    request.Credentials = credentials;
    // overwrite CookieContainer to store cookies
    request.CookieContainer = CookieJar; // CookieJar is a static CookieContainer object
    // get web response
    var response = request.GetResponse();
    // extract session token
    return response.Headers["SESSION_TOKEN"]; 
}

ServerCertificateBypass is a delegate which always returns true and userName, password are string variables. The session token expires after an hour, so I decided to use an internal Timer. Everytime it ticks, I call the above function and acquire a new token:

private void timer_Tick(object sender, EventArgs e)
{
    lock (syncLock)
    {
         // stop timer
         timer.Stop();
         // acquire and store token
         Task.Factory.StartNew(() => AcquireToken(Settings.UserCertificate, Settings.Password, Settings.UserName))
                  .ContinueWith(t =>
                  {
                      if (!string.IsNullOrEmpty(t.Result))
                      {
                           SaveToken(t.Result)
                           UpdateUI();
                       }
                   });
         // start timer
         timer.Start();
    }
}

If I set a frequency of 5 minutes, the above code works fine until the 6th or 7th tick, when it breaks at calling request.GetResponse() with the error:

The request was aborted: Could not create SSL/TLS secure channel

If I restart the application it works fine instantly again for a few ticks. If I set a frequency of 60 minutes it acquires a token only two ticks after which it breaks with the same error. I also have a button in UI to call the AcquireToken function manually. It works fine until I get that error after which it doesn't work either until I restart the application.

Setting ServicePointManager.SecurityProtocol as any variation of Tls1.1, Tls1.2 (as suggested in The request was aborted: Could not create SSL/TLS secure channel) does not work and I don't think it's supported by the server since it's not in the official documentation.

The question is why it breaks after it works fine a few times and after that it won't work anymore until restarting the application?

Any help is appreciated.

Update: I forgot to mention that the user certificate is valid until 2020 and that SSL3.0 and every version of TLS are enabled in Internet Explorer.

Alexandru Popa
  • 166
  • 3
  • 18
  • You are always getting the same error. Just changing the timer is taking longer before you get the error. The problem is with the connection and nothing to do with the times. First make sure tTLS is enabled from IE : Tools : Internet Options : Advanced. I suspect the certificate expired and may explain why it stopped working. – jdweng Mar 29 '19 at 14:56
  • All version of TLS and SSL3.0 are enabled in Internet Explorer. The user certificate is valid until 2020, so that can't be the problem. If I access that link in browser with the certificate, I can see the token in Network -> response -> Headers every time. It seems to be a problem with the application. – Alexandru Popa Mar 29 '19 at 14:59
  • What status do you see in the response? 200 OK? What version http? (1.0 or 1.1)? Can you sniff? Do you see [FIN] in the TCP indicating the connection closing? Do you see any retries in the TCP indicating messages aren't being acknowledged? – jdweng Mar 29 '19 at 15:13
  • Are you sure you're always connecting to the same (nation-wide?) server? It seems strange, at this time, that a server allows to use Ssl3/Tls1.0 with `Https` connections. Can you upgrade to, at least, .Net 4.5.2? – Jimi Mar 29 '19 at 15:28
  • @jdweng, For the first requests the status is 200 OK. The http version is 1.0. I have tried to ensure closing the connections manually from the code: using (var temp = new StreamReader(response.GetResponseStream(), true)) temp.ReadToEnd(); response.Close();, bute the result is the same. – Alexandru Popa Mar 29 '19 at 15:40
  • @Jimi, the server solution was implemented back in 2011 and not updated since. Upgrading the .NET framework would bring an avalanche of changes, since the solution depends on many projects and nugets that target 4.0. – Alexandru Popa Mar 29 '19 at 15:43
  • Try to dispose of set to null. Server must still think connection is opened and will not allow 2nd connection. From cmd.exe try >Netstat -a to see if connection is open or closed. – jdweng Mar 29 '19 at 15:55
  • Mind that, if you have a Proxy in-between, it could also be the Proxy that refuses the connection. Or there are different credentials, depending on the network path. But, I know nothing about your network, so.... Since *some* of the connection are, apparently, accepted, there might be something else that doesn't allow this (low-level security) handshake, at some point. – Jimi Mar 29 '19 at 15:56
  • Also, I suggested to install .Net 4.5.2, if it's not there, because it will enable your app, in an indirect way, to add to the ServicePointManager and use Tls1.2. You'll have to add the corresponding `int` value of Tls1.2 (3072) cast to `SecurityProtocolType`. Your .Net Framework doesn't know about it, but the system will use it in a handshake anyway. – Jimi Mar 29 '19 at 16:04
  • Another thing, see here [Which TLS version was negotiated?](https://stackoverflow.com/a/48675492/7444103). You could add the Tls *extraction* method shown there to determine what Tls version was negotiated with the server before posting the request. Maybe it can give you a clue of what's happening behind the scene. – Jimi Mar 29 '19 at 16:13

0 Answers0