I have recently been trying to make a SSL encrypted Server/Client in C#.
I have followed this tutorial on MSDN, however, it required a certificate to be created for the
First, do not create a certificate with the subject "CN=localhost" or equivalent. It is never going to be used in production so don't do it. Always issue it to your computer's hostname, e.g. CN="mycomputer", and use the host name when connecting to it rather than localhost. You can specify multiple names using the "subject alternate name" extension but makecert
does not appear to support it.
Second, when issuing a server SSL certificate, you need to add the "server authentication" OID to the enhanced key usage (EKU) extension of the certificate. Add the -eku 1.3.6.1.5.5.7.3.1
parameter to makecert
in your example. If you want to do client certificate authentication, use the "client authentication" OID of 1.3.6.1.5.5.7.3.2.
Lastly, the default certificate created by makecert uses MD5 as its hashing algorithm. MD5 is considered insecure and, although it will not affect your testing, get into the habit of using SHA1. Add -a sha1
to the makecert
parameters above to force SHA1. The default key size should also be increased from 1024-bits to 2048-bits but you get the idea.
In regard to your update:
One of the SslStream constructors allows you to provide a RemoteCertificateValidationCallback delegate. You should be able to put a breakpoint in the method that you provide to see what the actual error you're getting is. Check the SslPolicyErrors value sent in.
To get this to work with WCF it is neccessary to first create a self-signed root authority certificate and then use it to create the certificate for localhost.
I'm thinking the same might apply to your project as well, please have a look at this article How to: Create Temporary Certificates for Use During Development for details.
Have you tried:?
Create the certificate for a full domain name like example.net
(it's good to use example.net
, example.com
or example.org
for anything that's deliberately not a real name) or the name that will be used in live use if that's a single site and you know what it will be.
Update your hosts file so that it will use 127.0.0.1 for that name.
The answer can be found at SslStream.AuthenticateAsClient Method Remarks section:
The value specified for targetHost must match the name on the server's certificate.
If you use for the server a certificate whose subject is "CN=localhost", you must call AuthenticateAsClient with "localhost" as targetHost parameter to successfully authenticate it on the client side. If you would use "CN=David-PC" as certificate subject than you must call AuthenticateAsClient with "David-PC" as targetHost. SslStream checks the server identity by matching the server name that you intend to connect (and which you pass to AuthenticateAsClient) with the subject in the certificate received from the server. The practice is that the machine name that runs the server matches the name of the certificate's subject, and in the client you pass the same hostname to AuthenticateAsClient as you have used for opening the connection (with TcpClient in this case).
However there are other conditions to successfully establish SSL connection between servers and clients: the certificate passed to AuthenticateAsServer must have a private key, it must be trusted on the client machine and must not have any key usage restrictions related to usage for establishing SSL sessions.
Now related to your code sample, your problem is related to the generation and usage of the certificate.
You are not providing an issuer for your certificate and in this way it can't be trusted - this is the cause of the RemoteCertificateChainErrors Exception. I suggest to create a self signed certificate for development purposes specifying the -r option of makecert.
To be trusted a certificate must either be self-signed and placed in a trusted location in the Windows Certificate Store or must be linked with a chain of signatures to an already trusted Certificate Authority. So instead of the -ss My option which will place the certificate in the Personal store use -ss root that will place it in the Trusted Root Certification Authorities and it will be trusted on your machine (from the code I assume that your client is running on the same machine with the server and also the certificate is generated on it).
If you specify a output file to makecert it will export the certificate as .cer but this format contains only the public key, not the private key that is needed by the server to establish a SSL connection. The easiest way is to read the certificate from the Windows Certificate store in the server code. (You can also export it from the store in another format that allows storing the private key as described here Export a certificate with the private key and read that file in the server code).
You can find details about the makecert options used here Certificate Creation Tool (Makecert.exe)
In conclusion your code needs the following changes to run (tested with your latest code updates):
makecert -sr LocalMachine -ss root -r -n "CN=localhost" -sky exchange -sk 123456
serverCertificate = X509Certificate.CreateFromCertFile(certificate);
in the server code with:
X509Store store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
var certificates = store.Certificates.Find(X509FindType.FindBySubjectDistinguishedName, "CN=localhost", false);
store.Close();
if (certificates.Count == 0)
{
Console.WriteLine("Server certificate not found...");
return;
}
else
{
serverCertificate = certificates[0];
}
Please remember to replace "CN=localhost" with the subject of the certificate that you intend to use if you change the code later (in this situation should be the same value as the -n option passed to makecert). Also consider to use the machine name that runs the server instead of localhost in the server certificate's subject.
A server certificate's CN must be exactly the same as the server's domain name. I suppose, in your case the common name must be "localhost" (w/o quotes).
Important: for sure, as you might have read in other answers, never use CN="localhost"
in production.