I'm using .NET 4.5 c# to connect to a server that supports only TLS 1.2. (Apple Push Notification Server).
My code fails here:
sslStream.AuthenticateAsClient(server, myAppleCertificates, SslProtocols.Tls12, true);
I did a lot of testing and Googling and found the following:
1) This code works fine if i use a window 10 pc, but doesn't if I run this on my development server or production server. Windows Server 2008 R2.
2) All the pc's and servers are running .net 4.6+
3) I can add the line "ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;" - its still behaves exactly the same.
4) I can replicate the issue on Windows 10, buy simply changing it to: sslStream.AuthenticateAsClient(server, myAppleCertificates, SslProtocols.Tls11, true);
5) I have added the following registry entries and rebooted the server will no effect:
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Client]
"Enabled"=dword:00000001
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Client]
"DisabledByDefault"=dword:00000000
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Server]
"Enabled"=dword:00000001
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Server]
"DisabledByDefault"=dword:00000000
5) Server is up to date with windows updates. (actually... im not 100% on this one)
6) IISCrypto, says the protocols are there, and set as defaults correctly.
7) I can use Chrome to connect to APN, no problems, on my PC, and both the servers.
8) If i use Fiddler to test, it behaves exactly the same as my application, after i enable the TLS12 setting in the fiddler settings. I guess this is because Fiddler is build in .net 4.
Its as though even if you force .NET to use TLS1.2, it simply doesn't, and reverts back to 1.1. Does anyone have any more idea's here.
Thanks
Here is my test code:
public class SslTcpClient
{
private static Hashtable certificateErrors = new Hashtable();
public static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None) return true;
Console.WriteLine("Certificate error: {0}", sslPolicyErrors);
return false;
}
public static X509Certificate SelectLocalCertificate(object sender, string targetHost, X509CertificateCollection localCertificates, X509Certificate remoteCertificate, string[] acceptableIssuers)
{
Console.WriteLine("Client is selecting a local certificate.");
if (acceptableIssuers != null && acceptableIssuers.Length > 0 && localCertificates != null && localCertificates.Count > 0)
{
// Use the first certificate that is from an acceptable issuer.
foreach (X509Certificate certificate in localCertificates)
{
string issuer = certificate.Issuer;
if (Array.IndexOf(acceptableIssuers, issuer) != -1) return certificate;
}
}
if (localCertificates != null && localCertificates.Count > 0) return localCertificates[0];
return null;
}
public static void RunClient(string server)
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
TcpClient client = new TcpClient(server, 443);
Console.WriteLine("Client connected.");
SslStream sslStream = new SslStream(client.GetStream(), false, new RemoteCertificateValidationCallback(ValidateServerCertificate), null);
Console.WriteLine("SSL stream obtained");
try
{
X509Certificate localCertificate = new X509Certificate("VOIPPush.p12", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
Console.WriteLine("Local Certificte features");
Console.WriteLine("=========================");
Console.WriteLine("Issuer: " + localCertificate.Issuer);
Console.WriteLine("Subject: " + localCertificate.Subject);
Console.WriteLine("CertHash: " + localCertificate.GetCertHashString());
Console.WriteLine("EffectiveDate: " + localCertificate.GetEffectiveDateString());
Console.WriteLine("ExpirationDate: " + localCertificate.GetExpirationDateString());
Console.WriteLine("Format: " + localCertificate.GetFormat());
Console.WriteLine("HashCode: " + localCertificate.GetHashCode().ToString());
Console.WriteLine("KeyAlgorithm: " + localCertificate.GetKeyAlgorithm());
Console.WriteLine("KeyAlgorithmParameters: " + localCertificate.GetKeyAlgorithmParametersString());
Console.WriteLine("PublicKey: " + localCertificate.GetPublicKeyString());
Console.WriteLine("SerialNumber: " + localCertificate.GetSerialNumberString());
X509CertificateCollection localCertificates = new X509CertificateCollection();
localCertificates.Add(localCertificate);
sslStream.AuthenticateAsClient(server, localCertificates, SslProtocols.Tls12, true);
Console.WriteLine("Authenticated as client");
X509Certificate remoteCertificate = sslStream.RemoteCertificate;
Console.WriteLine("Remote Certificte features");
Console.WriteLine("==========================");
Console.WriteLine("Issuer: " + remoteCertificate.Issuer);
Console.WriteLine("Subject: " + remoteCertificate.Subject);
Console.WriteLine("CertHash: " + remoteCertificate.GetCertHashString());
Console.WriteLine("EffectiveDate: " + remoteCertificate.GetEffectiveDateString());
Console.WriteLine("ExpirationDate: " + remoteCertificate.GetExpirationDateString());
Console.WriteLine("Format: " + remoteCertificate.GetFormat());
Console.WriteLine("HashCode: " + remoteCertificate.GetHashCode().ToString());
Console.WriteLine("KeyAlgorithm: " + remoteCertificate.GetKeyAlgorithm());
Console.WriteLine("KeyAlgorithmParameters: " + remoteCertificate.GetKeyAlgorithmParametersString());
Console.WriteLine("PublicKey: " + remoteCertificate.GetPublicKeyString());
Console.WriteLine("SerialNumber: " + remoteCertificate.GetSerialNumberString());
}
catch (AuthenticationException e)
{
Console.WriteLine("Exception: {0}", e.ToString());
client.Close();
return;
}
byte[] messsage = Encoding.UTF8.GetBytes(@"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n");
// no use in writing more, this is just a test...
Console.WriteLine("Writing to sslStream...");
sslStream.Write(messsage);
Console.WriteLine("Writing done");
sslStream.Flush();
Console.WriteLine("Reading Response...");
string serverMessage = ReadMessage(sslStream);
Console.WriteLine("Server says: {0}", serverMessage);
client.Close();
Console.WriteLine("Client closed.");
}
static string ReadMessage(SslStream sslStream)
{
// Read the message sent by the server.
// The end of the message is signaled using the
// "<EOF>" marker.
byte[] buffer = new byte[2048];
StringBuilder messageData = new StringBuilder();
int bytes = -1;
do
{
bytes = sslStream.Read(buffer, 0, buffer.Length);
// Use Decoder class to convert from bytes to UTF8
// in case a character spans two buffers.
Decoder decoder = Encoding.UTF8.GetDecoder();
char[] chars = new char[decoder.GetCharCount(buffer, 0, bytes)];
decoder.GetChars(buffer, 0, bytes, chars, 0);
messageData.Append(chars);
// Check for EOF.
if (messageData.ToString().IndexOf("<EOF>") != -1)
{
break;
}
} while (bytes != 0);
return messageData.ToString();
}
public static void Main(string[] args)
{
SslTcpClient.RunClient("api.push.apple.com");
Console.ReadLine();
}
}