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);
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.