Jakob Tinhofer
Jakob Tinhofer

Reputation: 377

Can't get C# SslStream to work with my X509 certs

I am kind of new to doing security and encryption, so if I just made a very dumb mistake, sorry in advance. I need a server and a client communicating through a secure connection using SslStream. But my certificates don't work. I get the following error: System.NotSupportedException: 'The server mode SSL must use a certificate with the associated private key.' My code was the microsoft example given in the documentation: https://learn.microsoft.com/en-us/dotnet/api/system.net.security.sslstream?view=netframework-4.8ss

I tried:

All except the last one gave the System.NotSupportedException: 'The server mode SSL must use a certificate with the associated private key.' exception. Does this mean that self-signed certificates don't work? Do I need to buy a certificate?

Edit: Here is the code I used. It is the modified example (Sorry if it is terribly coded on my part) and is executable, simulating server and client and throws the exception:

class Program
{

    static void Main(string[] args)
    {
        //Temporarily added the arguments here for you to see
        args = new string[2] { @"C:\Users\jacke\Documents\CA\TempCert.cer", "FakeServerName" };
        Console.WriteLine("Starting server in seperate thread...");
        Task t = Task.Run(() => { Server.Initialize(args[0]); });
        Task.Delay(500).Wait();
        Client.RunClient(args[1]);
    }
}

public static class Server
{
    private static X509Certificate cert;
    private static TcpListener server;

    public static void Initialize(string certificate)
    {
        cert = X509Certificate.CreateFromCertFile(certificate);
        server = new TcpListener(IPAddress.Any, 12321);
        server.Start();
        while (true)
        {
            Console.WriteLine("Waiting for a client to connect...");

            TcpClient client = server.AcceptTcpClient();
            ProcessClient(client);
        }
    }


    private static void ProcessClient(TcpClient client)
    {

        SslStream sslStream = new SslStream(client.GetStream(), false);
        try
        {
            sslStream.AuthenticateAsServer(cert, clientCertificateRequired: false, checkCertificateRevocation: true);
            sslStream.ReadTimeout = 5000;
            sslStream.WriteTimeout = 5000;   
            Console.WriteLine("Waiting for client message...");
            string messageData = Helpers.ReadMessage(sslStream);
            byte[] message = Encoding.UTF8.GetBytes("Hello from the server.<EOF>");
            Console.WriteLine("Sending hello message.");
            sslStream.Write(message);
        }
        catch (AuthenticationException e)
        {
            Console.WriteLine("Exception: {0}", e.Message);
            if (e.InnerException != null)
            {
                Console.WriteLine("Inner exception: {0}", e.InnerException.Message);
            }
            Console.WriteLine("Authentication failed - closing the connection.");
            sslStream.Close();
            client.Close();
            return;
        }
        finally
        {                
            sslStream.Close();
            client.Close();
        }
    }



}

public static class Client
{
    private static Hashtable certificateErrors = new Hashtable();
    public static bool ValidateServerCertificate(
          object sender,
          X509Certificate certificate,
          X509Chain chain,
          SslPolicyErrors sslPolicyErrors)
    {
        if (sslPolicyErrors == SslPolicyErrors.None)
            return true;

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


    public static void RunClient(string serverName)
    {
        TcpClient client = new TcpClient("localhost", 12321);
        Console.WriteLine("Client connected.");
        SslStream sslStream = new SslStream(
            client.GetStream(),
            false,
            new RemoteCertificateValidationCallback(ValidateServerCertificate),
            null
            );
        try
        {
            sslStream.AuthenticateAsClient(serverName);
        }
        catch (AuthenticationException e)
        {
            Console.WriteLine("Exception: {0}", e.Message);
            if (e.InnerException != null)
            {
                Console.WriteLine("Inner exception: {0}", e.InnerException.Message);
            }
            Console.WriteLine("Authentication failed - closing the connection.");
            client.Close();
            return;
        }
        byte[] messsage = Encoding.UTF8.GetBytes("Hello from the client.<EOF>");
        sslStream.Write(messsage);
        string serverMessage = Helpers.ReadMessage(sslStream);
        Console.WriteLine("Server says: {0}", serverMessage);

        client.Close();
        Console.WriteLine("Client closed.");
    }


}

public static class Helpers
{
    public static string ReadMessage(SslStream sslStream)
    {
        // Read the  message sent by the server.
        // The end of the message is signaled using the
        // "<EOF>" marker.
        byte[] buffer = new byte[2048];
        StringBuilder messageData = new StringBuilder();
        int bytes = -1;
        do
        {
            bytes = sslStream.Read(buffer, 0, buffer.Length);
            Decoder decoder = Encoding.UTF8.GetDecoder();
            char[] chars = new char[decoder.GetCharCount(buffer, 0, bytes)];
            decoder.GetChars(buffer, 0, bytes, chars, 0);
            messageData.Append(chars);
            // Check for EOF.
            if (messageData.ToString().IndexOf("<EOF>") != -1)
            {
                break;
            }
        } while (bytes != 0);

        return messageData.ToString();
    }
}

And here is how I created the certificates (as explained in the post I linked above):

makecert -sv RootCATest.pvk -r -n "CN=FakeServerName" RootCATest.cer
makecert -ic RootCATest.cer -iv RootCATest.pvk -n "CN=FakeServerName" -sv
TempCert.pvk -pe -sky exchange TempCert.cer
cert2spc TempCert.cer TempCert.spc
pvkimprt -pfx TempCert.spc TempCert.pvk

Additional information I entered with the commands above:

Then I imported the .pfx file in the local certificate store (I also tried machine wide earlier) and let the program pick the right store. It warned me that all certificates by the CA will be trusted and I should contact the CA to check this was indeed their certificate, but I proceeded. Then I ran the code (using the 'TempCert.cer' file I just created) and got the error. Any advice is highly appreciated!

Upvotes: 1

Views: 2008

Answers (1)

C.Evenhuis
C.Evenhuis

Reputation: 26446

If I recall correctly, a .cer file does not include a private key, and I assume by loading a certificate from file the certificate store is not checked. You could export a .pfx and include the private key in the export.

I don't know why the .pvk attempt failed though (never tried that format myself) - SslStream can definitely work with self-signed certificates; the client side just has to ignore the validation failure.

However if you've already imported it into the store, you should be able to load it directly:

using System.Security.Cryptography.X509Certificates;

// ...

using (X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine))
{
    store.Open(OpenFlags.ReadOnly);
    var certs = store.Certificates.Find(X509FindType.FindBySubjectName, "FakeServerName", false);
    return new X509Certificate2(certs[0]);
}

Respoding to the error in your comment:

The credentials supplied to the package were not recognized

I don't know if I got exactly that message, but I do remember having to grant access to the user the app runs into. You'd have to open the correct certificates tool:

https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/how-to-view-certificates-with-the-mmc-snap-in (run mmc, select file/add snap-in, choose certificates)

Then grant user(s) access to the private key:

https://docs.secureauth.com/display/KBA/Grant+Permission+to+Use+Signing+Certificate+Private+Key (right click the certificate, select all tasks/manage private keys)

Upvotes: 2

Related Questions