7

I was reading MSDN info articles for quite a long time and still I fail to understand it.

Based on the assumption that client Authentication is not required:

1.When I call SslStream.AuthenticateAsServer(...) do I call this method on the server side or on the client side?

2.When establishing a SslStream is it only the responsibility of the server to establish the SslStream or both server and client?

3.If it is only the responsibility of the server, does it mean that the client can just use regular send() and receive() operations without creating a SslStream by himself?

4.Does the client need to get the certificate file in order to authenticate the server?

Thank you very much in advance, I really could not find much information about this topic and I've been searching for this information for a long time...

Chronicler
  • 45
  • 4
WeinForce
  • 1,264
  • 2
  • 11
  • 17

1 Answers1

9

EDIT: the MSDN has a complete working example at the bottom of this page: https://msdn.microsoft.com/en-us/library/system.net.security.sslstream?f=255&MSPPError=-2147217396 - so you should really start experimenting there because that example has it all.

Original answer:

I must preface this answer that "client authentication not required" is the case for most of the SSL implementations. Client authentication is rare: you're likely to see it in VPN apps, the banking industry and other secure apps. So it would be wise when you are experimenting with SslStream() to start without client authentication.

When you browse to an HTTPS website, you don't authenticate your browser with a client cert, instead you just want to confirm the server name you are connecting to matches up to the CNAME found in the cert and that the server cert is signed by a CA that your machine trusts - there's more to it, but essentially that's what it boils down to.

So, having said that, let me answer your questions:

1) SslStream.AuthenticateAsServer(...) is done ONLY on server side with the server 509 certificate. On the client side, you must call SslStream.AuthenticateAsClient(serverName) with server name being the CNAME (common name) of your certificate (example: "domain.com")

2) SslStream must be created for both the client and the server. You create it simply by "wrapping" a TcpClient NetworkStream around it (for example, but there are other methods)

Example for the server:

// assuming an 509 certificate has been loaded before in an init method of some sort
X509Certificate serverCertificate = X509Certificate2.CreateFromCertFile("c:\\mycert.cer"); // for illustration only, don't do it like this in production
...

// assuming a TcpClient tcpClient was accepted somewhere above this code
slStream sslStream = new SslStream(tcpClient.GetStream(), false);
sslStream.AuthenticateAsServer(
                serverCertificate,
                false, 
                SslProtocols.Tls, 
                true);

3) No. The communication is encrypted on both ends. So both sides must use SslStream. Using receive() and send() on the client would yield binary encrypted data.

4) No. The client passes a callback method to the SslStream creation in order to validate the certificate received by the server.

Example:

// assuming a TcpClient tcpClient was connected to the server somewhere above this code
SslStream sslStream = new SslStream(
            tcpClient.GetStream(),
            false,
            new RemoteCertificateValidationCallback(ValidateServerCertificate),
            null
            ); 
sslStream.AuthenticateAsClient(serverName); // serverName: "domain.com" for example

then somewhere else in your code:

public static bool ValidateServerCertificate(
          object sender,
          X509Certificate certificate,
          X509Chain chain,
          SslPolicyErrors sslPolicyErrors)
    {
        if (sslPolicyErrors == SslPolicyErrors.None) {
            return true;
        }

        Console.WriteLine("Certificate error: {0}", sslPolicyErrors);

        // refuse connection
        return false;
    }
Max
  • 1,049
  • 7
  • 9
  • can i send in the serverName the Ip address of the server as a string? – WeinForce Jan 26 '17 at 01:01
  • Yes, you can (not recommended in prod) but in that case you will get an SSL failure and so on your client side, just always return true in the ValidateServerCertificate() function; or do whatever seems appropriate for your purpose. – Max Jan 26 '17 at 02:02
  • why would i get an error if i use `sslStream.AuthenticateAsClient(serverIpAsString)` since serverName is translated to IPAddress anyways? also where is the value of `ValidateServerCertificate` returned to? – WeinForce Jan 26 '17 at 02:30
  • and also i'm getting Error: the remote certificate is invalid according to the validation procedure. i would like to fix it and not just make it to return true all the time, beacuse this solution need to be secure. – WeinForce Jan 26 '17 at 03:58
  • because that's how it functions. I was incorrect earlier, you cannot use an IP for the servername because the server name must match the name on the certificate, period. The IP is good for the initial connection to the server, that's all. There's a complete working example on this MSDN page for both the server and the client - that's actually where some of the code I posted above initially came from years ago. https://msdn.microsoft.com/en-us/library/system.net.security.sslstream%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396 - Give it a try and experiment. – Max Jan 26 '17 at 07:17
  • i have to mention that i got an error: `The remote certificate is invalid according to the validation procedure` but i fixed it with adding the certificate to the Certificate Store Trusted CA. The Question is: how can i do it programmatically? – WeinForce Jan 26 '17 at 09:18
  • Add this at the beginning of your client program (not recommended in production): `ServicePointManager.ServerCertificateValidationCallback += new RemoteCertificateValidationCallback((Object i_oObject, X509Certificate i_oCert, X509Chain i_oChain, SslPolicyErrors i_oPolicyErrors) => { return true; } );` – Max Jan 27 '17 at 02:39
  • To get around the problem of the certificate name not matching due to connecting over local machine name or IP address from the test machine, you can add an entry in your hosts file, which is like a pre-DNS name resolver, so you can put in the IP you're connecting to with the name on the certificate. On Windows the hosts file is at c:\windows\system32\drivers\etc and is called hosts with no extension, and must be edited in Administrator mode, as it's a slightly protected file. This way, when you run test from that workstation, it will have the correct host name to validate the certificate. – Shad Dec 29 '20 at 01:16
  • Yes, that's a good tip. We do this all the time :) – Max Jan 01 '21 at 01:42