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.