Lots of people have seen the client certificate selection dialog when we use IE to browser HTTPS resources. Few people have thought about and understand why the certificate selection dialog doesn't include all certificate in personal store. The fact is: IE has received an acceptable issuer list from the web server side. IE will show nothing in the certificate selection dialog if there is no certificate which has "Client Authentication" usage and issued by any issuer (aka CA, root or intermediate CA) on that acceptable issuer list.

Knowing the acceptable issuer list of a certain web server is one of the key factors to troubleshotting HTTPS connectivity issues. We can tracing the acceptable issuers list by the C# code as below (sorry for long paragraph, joycode use to fancy code collapse feature which seems gone) :
using System;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using System.Net.Security;
using System.Security.Authentication;
using System.Text;
using System.Security.Cryptography.X509Certificates;
using System.IO;
namespace ListServerTrustedCAs
{
class Program
{
static void Main(string[] args)
{
if (args.Length != 1)
{
Console.WriteLine("\r\nUsage: ListServerTrustedCAs <servername>\r\n");
return;
}
string serverName = args[0];
TcpClient client = new TcpClient(serverName, 443);
Console.WriteLine("Client connected.");
SslStream sslStream = new SslStream(
client.GetStream(),
false,
new RemoteCertificateValidationCallback(ValidateServerCertificate),
new LocalCertificateSelectionCallback(SelectLocalCertificate)
);
try
{
sslStream.AuthenticateAsClient(serverName);
}
catch (AuthenticationException e)
{
Console.WriteLine("Exception: {0}", e.ToString());
client.Close();
return;
}
client.Close();
Console.WriteLine("Client closed.");
}
public static bool ValidateServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
Console.WriteLine("ValidateServerCertificate. \r\n Server-side cert: " + certificate.Subject);
return true; // do nothing.
}
public static X509Certificate SelectLocalCertificate(
object sender,
string targetHost,
X509CertificateCollection localCertificates,
X509Certificate remoteCertificate,
string[] acceptableIssuers)
{
if (acceptableIssuers != null)
{
foreach (string issuer in acceptableIssuers)
{
Console.WriteLine("<" + issuer + ">");
}
}
return null;
}
}
}
|
The code above can also print out the IIS's Server Certificate.
The last but most important thing is: if your web server is IIS, you get to turn on SSLAlwaysNegoClientCert in IIS. Otherwise, the code above gets empty acceptable issuer list. The command line to turn on SSLAlwaysNegoClientCert is:
| Cscript.exe adsutil.vbs SET w3svc/SSLAlwaysNegoClientCert “true” |
But the trick thing is: even without SSLAlwaysNegoClientCert turned on, IE still can get IIS's trusted CA list (so that IE shows eligible certificates in the selection dialog). I don't know how IE talks to IIS and force IIS to send back trusted CA list. Could be a undocumented flag in the hello message?
Reference (Must-Read!):
RFC 2246 "The TLS Protocol Version 1.0", section 7.4.4 "Certificate request".