3

Client: Visit
1. https://host1.com/
2. https://host2.com/

Server: There are two certificates.
certificates1.pfx CN=host1.com and certificates2.pfx CN=host2.com

use wireshark
Client visit https://host1.com/
1: C --> S SYN
2: C <-- S SYN,ACK
3: C --> S ACK
4: C --> S Client Hello (Contain Server Name: host1.com)
... How do I select certificate1 in C#
5: C <-- S Server Hello, Certificate, Server Hello Done

Client visit https://host2.com/
1: C --> S SYN
2: C <-- S SYN,ACK
3: C --> S ACK
4: C --> S Client Hello (Contain Server Name: host2.com)
... How do I select certificate2 in C#
5: c <-- S Server Hello, Certificate, Server Hello Done

SslStream sslStream = new SslStream(
  clientStream,
  false,
  new RemoteCertificateValidationCallback(ValidateServerCertificate),
  new LocalCertificateSelectionCallback(SelectLocalCertificate)
);

X509Certificate2 certificate = new X509Certificate2("certificates1.pfx");

sslStream.AuthenticateAsServer(certificate , false, SslProtocols.Tls | SslProtocols.Ssl3 | SslProtocols.Ssl2, true);

private X509Certificate SelectLocalCertificate(object sender, string targetHost, X509CertificateCollection localCertificates, X509Certificate remoteCertificate, string[] acceptableIssuers)
{
  //In Debug, targetHost is empty string and remoteCertificate=null
  //I can't return right Certificates
  return null;
}
private bool ValidateServerCertificate( object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
    return true;
}
EricLaw
  • 56,563
  • 7
  • 151
  • 196
ruijiexie
  • 97
  • 1
  • 7
  • I have edited your title. Please see, "[Should questions include “tags” in their titles?](http://meta.stackexchange.com/questions/19190/)", where the consensus is "no, they should not". – John Saunders Sep 15 '13 at 01:24

3 Answers3

2

While SslStream itself doesn't support SNI, I've confirmed it's possible to workaround. On the server, if you read some bytes from the NetworkStream before starting SslStream, you can see that the initial packet sent from client to server is in fact the client hello, which includes the requested server name.

There is a problem because NetworkStream doesn't support peeking bytes... So you'd have to use a wrapper stream class. (There is an implementation here: https://stackoverflow.com/a/7281113/1726692 ).

There is another problem - Once you get the bytes, you have to figure out how to process them. I'm sure this is implementation specific, and governed by dozens of standards... I don't currently have an implementation of something to parse those initial bytes, but when I connect a Win7 SSlStream client to a Win8 SslStream server, and I capture the first packet from client to server, I can very clearly see the requested servername sent to the server in those bytes, available to the server via the above PeekableStream, prior to starting the SslStream on the server.

So it's definitely possible. Question is where to find a reliable implementation.

Community
  • 1
  • 1
Edward Ned Harvey
  • 6,525
  • 5
  • 36
  • 45
1

It is not possible to select a certificate using a LocalCertificateSelectionCallback delegate with SslStream acting as a server. You can specify only one certificate in this case, as the first parameter for the AuthenticateAsServer method.

The documentation for SslStream Class on MSDN also mentions the usage of the LocalCertificateSelectionCallback delegate on the client:

If the server requires client authentication, the client must specify one or more certificates for authentication. If the client has more than one certificate, the client can provide a LocalCertificateSelectionCallback delegate to select the correct certificate for the server.

And finally you can check this question that seems to be related with your issue Does SslStream use LocalCertificateSelectionCallback when acting as a server?

Community
  • 1
  • 1
andrei m
  • 1,157
  • 10
  • 15
  • 1
    Good answer. It is technically possible to select the certificate by performing a .Peek() on the underlying Socket object and parsing the SNI TLS extension (if present) in the ClientHello then using that information to pick the certificate passed to AuthenticateAsServer. But it's non-trivial, to be sure. – EricLaw Sep 15 '13 at 05:51
  • @EricLaw You are right, some manual processing in attempt to retrieve the hostname of the server that the client is trying to connect from the SNI TLS extension can be done. But this is not provided out of the box by SslStream. Besides some custom implementation, another option would be to use another SSL library that supports SNI. And even in this case it wouldn't be fully reliable in case some clients are not able to use TLS and fallback to SSL2 or SSL3. – andrei m Sep 15 '13 at 07:21
  • Indeed: beyond SSL2/SSL3, Windows XP-based clients (IE, Office, etc) as well as some mobile device clients support TLS but do not support the SNI TLS extension. In those cases, there's no way for any server to distinguish the hostname that the client wants, and the only approach is to return a single certificate containing multiple hostnames in the SubjectAltNames field. – EricLaw Sep 16 '13 at 04:42
0

I'm not sure why targetHost is blank.

You can also try checking remoteCertificate.Subject to identity the server. Your ValidateServerCertificate method should make sure that this matches the host.

hwiechers
  • 14,583
  • 8
  • 53
  • 62