I'm having no luck getting client certificates working with my SslStream project. No matter what I do, I can't get it to actually use the client certificate, despite the fact that all certificates are valid and trusted, and I have imported the CA certificate for the ones I generated myself, and it just doesn't work. I must be missing something, but I've been over it dozens of times, reviewed documentation, examples, and hours of google searching, and I just can't get it to work. What am I doing wrong?
The Client:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Security;
using System.Net.Sockets;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.Text;
namespace SslClient
{
class SslClientProgram
{
static void Main(string[] args)
{
TcpClient client = new TcpClient("localhost", 443);
SslStream stream = new SslStream(client.GetStream(), false, VerifyServerCertificate, null);
Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly();
string location = assembly.Location;
int pos = location.LastIndexOf('\\');
location = location.Substring(0, pos);
X509Certificate2 certificate = new X509Certificate2(location + "\\my.client.certificate.pfx", "password");
stream.AuthenticateAsClient("my.host.name", new X509Certificate2Collection(certificate), System.Security.Authentication.SslProtocols.Tls, false);
StreamReader reader = new StreamReader(stream);
StreamWriter writer = new StreamWriter(stream);
while (true)
{
string line = System.Console.ReadLine();
writer.WriteLine(line);
writer.Flush();
if (line == "close") break;
line = reader.ReadLine();
System.Console.WriteLine("Received: {0}", line);
}
stream.Close();
}
private static bool VerifyServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
return true;
}
}
}
The Server:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Security;
using System.Net.Sockets;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.Text;
namespace SslServer
{
class SslServerProgram
{
static void Main(string[] args)
{
TcpListener server = new TcpListener(System.Net.IPAddress.Loopback, 443);
server.Start();
TcpClient client = server.AcceptTcpClient();
SslStream stream = new SslStream(client.GetStream(), false, VerifyClientCertificate, null);
Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly();
string location = assembly.Location;
int pos = location.LastIndexOf('\\');
location = location.Substring(0, pos);
X509Certificate2 certificate = new X509Certificate2(location + "\\my.server.certificate.pfx", "password");
stream.AuthenticateAsServer(certificate, false, System.Security.Authentication.SslProtocols.Tls, false);
if (stream.RemoteCertificate != null)
{
System.Console.WriteLine(stream.RemoteCertificate.Subject);
}
else
{
System.Console.WriteLine("No client certificate.");
}
StreamReader reader = new StreamReader(stream);
StreamWriter writer = new StreamWriter(stream);
bool clientClose = false;
while (!System.Console.KeyAvailable)
{
System.Console.WriteLine("Waiting for data...");
string line = reader.ReadLine();
System.Console.WriteLine("Received: {0}", line);
if (line == "close")
{
clientClose = true;
break;
}
writer.WriteLine(line);
writer.Flush();
}
if (!clientClose) System.Console.ReadKey();
stream.Close();
server.Stop();
}
private static bool VerifyClientCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
return true;
}
}
}
No matter what I try, the server always says "No client certificate."
As it turns out, AuthenticateAsServer
is the key here - more specifically, the second parameter.
If clientCertificateRequired
is false
, it will completely ignore client certificates, even if one is specified by the client, but if it is true
, it will allow them, but does not throw an exception if no client certificate is specified.
Silly me - I thought clientCertificateRequired
set to true meant that it would be actually required, because the .Net documentation describes it as:
"A Boolean value that specifies whether the client must supply a certificate for authentication."* (emphasis mine)
My expectation was that if it was true
, and I did not send a client certificate, then it would fail. This is a clear case of less than completely accurate documentation on the part of Microsoft.
Update: The latest documentation for the clientCertificateRequired
parameter includes the phrase "Note that this is only a request -- if no certificate is provided, the server still accepts the connection request."