Search code examples
c#.netsslssl-certificatex509certificate

How to authenticate as Server and Client SslStream


I've been trying to implement an SSL connexion with a server and client side using certificate but I've been hitting on a nail and can't seem to be able to authenticate, after numerous posts and threads carefully read, I'd hope you could help me find the problem source.

I allways catch an exception thrown at the AuthenticateAsServer() line :

System.Security.Authentication.AuthenticationException : 'Authentication failed, see inner exception.' Win32Exception : the credentials supplied to the package were not recognized.

for now, i'm just trying to call the server through chrome with this url, I did try first with my client side code, but my authenticateAsClient() side wasn't the source of the problem since even with google chrome I'm not abble to authenticate:

https://192.168.1.113:32581

using port 32581 for tests, computer.IpPort=32581

server ip is 192.168.1.103, computer.IpAddress="192.168.1.103"

here are all the steps I did :

created a certificate using PowerShell New-SelfSignedCertificate

New-SelfSignedCertificate -Subject DESKTOP-12345

here is my server side code :

getServerCert() works and returns the right certificat.

ValidateServerCertificate() for now on returns true.

private readonly Computer computer;
private TcpClient TCP_Client;
private NetworkStream TCP_Stream;
private SslStream SSL_Stream;

private X509Certificate2 clientCertificate;
private ConcurrentQueue<string> TCP_pendingCommands = new ConcurrentQueue<string>();

private void TCP_Listen()
{
    try
    {
        ServerCertificate = getServerCert();

        if (ServerCertificate == null)
        {
            throw new Exception("Client certificate is not set.");
        }

        // Listen
        TCP_Listener = new TcpListener(IPAddress.Any, computer.IpPort);
        TCP_Listener.Start();
        while (true)
        {
            Console.WriteLine("Waiting for a client to connect...");
            Thread.Sleep(2000);
            TCP_Client = TCP_Listener.AcceptTcpClient();
            ProcessClient();
        }
    }
    catch (Exception e)
    {
        Disconnect();
        Console.WriteLine(e.Message);
        Thread.Sleep(1000);
    }
}
private void ProcessClient()
{
    try
    {
        IPEndPoint remoteIpEndPoint = TCP_Client.Client.RemoteEndPoint as IPEndPoint;
        computer.IpAddress = remoteIpEndPoint.Address.ToString();

        TCP_Stream = TCP_Client.GetStream();
        TCP_Stream.ReadTimeout = Timeout.Infinite;

        SSL_Stream = new SslStream(TCP_Stream, false, ValidateServerCertificate);
        SSL_Stream.AuthenticateAsServer(ServerCertificate, false, SslProtocols.Tls12, false);

        if (!SSL_Stream.IsAuthenticated)
            throw new Exception("Failed to connect.");

        new Thread(() => TCP_Write(TCP_Client))
        {
            IsBackground = true,
            Name = "SSL server TCP Write thread"
        }.Start();

        new Thread(() => TCP_Read(TCP_Client))
        {
            IsBackground = true,
            Name = "SSL server TCP Read thread"
        }.Start();

        TCP_pendingCommands = new ConcurrentQueue<string>();
    }
    catch (Exception e)
    {
        Disconnect();
        Console.WriteLine(e.Message);
        Thread.Sleep(1000);
    }
}
    
private bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
    // lets just return true here
    return true;
}

private X509Certificate getServerCert()
{
    X509Store store = new X509Store(StoreName.My,
      StoreLocation.LocalMachine);
    store.Open(OpenFlags.ReadOnly);

    X509Certificate2 foundCertificate = null;
    foreach (X509Certificate2 currentCertificate
       in store.Certificates)
    {
        if (currentCertificate.IssuerName.Name
           != null && currentCertificate.IssuerName.
           Name.Equals("CN=DESKTOP-12345"))
        {
            foundCertificate = currentCertificate;
            break;
        }
    }
    return foundCertificate;
}

here is my client side if needed even thought I didn't use it sucessfully yet:

private void Connect()
        {
    try
    {
        TCP_Client = new TcpClient(computer.IpAddress, computer.IpPort)
        {
            NoDelay = true
        };
        TCP_Stream = TCP_Client.GetStream();
        TCP_Stream.ReadTimeout = Timeout.Infinite;

        SSL_Stream = new SslStream(TCP_Stream, false, new RemoteCertificateValidationCallback(ValidateServerCertificate), null);
        SSL_Stream.AuthenticateAsClient("DESKTOP-12345");

        byte[] buffer = new byte[4096];
        int bytesRead = SSL_Stream.Read(buffer, 0, buffer.Length);



        if (!SSL_Stream.IsAuthenticated)
            throw new Exception("Failed to connect.");

        RaisePropertyChanged(nameof(Power));
        computer.RaiseComputerPropertyChanged(nameof(Power));
        computer.SetComputerConnected(true);
        TCP_pendingCommands = new ConcurrentQueue<string>();
    }
    catch (Exception e)
    {
        Disconnect();
        Debug.Writeline(e.Message);
        Thread.Sleep(1000);
    }
}
public 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 true;
}

EDIT : I also used openssl with those steps, got the same exception:

generate certificate and key:

req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout private.key -out certificate.crt

used this code part instead of the ServerCertificate = getServerCert();:

string certFilePath = "C:\\Path\\To\\certificate.crt";
string privateKeyPath = "C:\\Path\\To\\private.key";
ServerCertificate = X509Certificate2.CreateFromPemFile(certFilePath, privateKeyPath);

Solution

  • the credentials supplied to the package were not recognized generally means "Windows S/Channel can't access the private key", with the top two sub-reasons being "there is no private key" or "the private key is ephemeral". CreateFromPemFile makes ephemeral private keys, so that's definitely the case with that approach. There's a trivial workaround, though:

    using (X509Certificate2 pemLoad = X509Certificate2.CreateFromPemFile(certFilePath, privateKeyPath))
    {
        ServerCertificate = new X509Certificate2(pemLoad.Export(X509ContentType.Pkcs12));
    }
    

    The PKCS12/PFX loader uses a different key loading mechanism than the PEM loader, and the PFX loader works with SslStream.