I have made a client-server connection with TLS/SSL handshake (using SSLStream) and I want to make my client to verify the server using a certificate signed by personal CA made by OpenSSL. I have successfully tested it as a localhost connection, however, when I tested it as LAN it failed as it can't verify the chain (obviously, the client machine did not have the cert in the local store). I have made a chain.pem file however.
So here is my question:
- How can I verify a remote server without needing to install the CA cert?
- If I have to use the chain, how can I use the chain.pem file which has been created using OpenSSL?
- If I still have to install the server cert and CA cert to the client, how to install it automatically to client machine?
Here are the code for the server:
namespace Client_Server_SSL2.Server
{
public sealed class Server
{
static X509Certificate2 _ServerCert = null;//Var to store the certificate
/// <summary>
/// Start a server and try establishing connection with a certificate request for authentication.
/// </summary>
/// <param name="certificate">Name of the file containing the certificate.</param>
public static void StartServer(string certificate)
{
//Create X509Cert from the certificate param.
_ServerCert = new X509Certificate2(certificate, "password");
//bool validation = new RemoteCertificateValidationCallback(_ServerCert);
//Create TCP/IP(IPv4) socket and listen to incoming connection;
TcpListener listener = new TcpListener(IPAddress.Any,20018);
listener.Start();
while (true)//infinite loop to listen
{
Console.WriteLine("Waiting for incoming connection...\n" +
"Press ctrl + C to terminate!");
TcpClient client = listener.AcceptTcpClient();//Accept incoming connection
Client_Proc(client);//Client process and activity.
}
}
/// <summary>
/// All of the client processes in the server will be runned from here.
/// </summary>
/// <param name="client"></param>
static void Client_Proc(TcpClient client)
{
//If client connected, Create SSLStream using Client's network stream.
SslStream sslStream = new SslStream(client.GetStream(), false);
try
{
//Authenticate server but don't require the client to authenticate
sslStream.AuthenticateAsServer(_ServerCert, clientCertificateRequired: false, checkCertificateRevocation: true);
//Display properties and setting for authenticated connection. A.K.A. Display Status
DisplaySecurityLevel(sslStream);
DisplaySecurityService(sslStream);
DisplayCertificateInfo(sslStream);
DisplaySysProp(sslStream);
//Set timeout for the read and write to 5 sec (5000 in ms).
sslStream.ReadTimeout = 5000;
sslStream.WriteTimeout = 5000;
//Read a message from the client
Console.WriteLine("Waiting for client message...");
string messageData = ReadMessage(sslStream);
Console.WriteLine("Received: {0}", messageData);
// Write a message to the client.
byte[] message = Encoding.UTF8.GetBytes("Hello from the server.<EOF>");
Console.WriteLine("Sending hello message.");
sslStream.Write(message);
}
catch (AuthenticationException msg)//in case of error
{
Console.WriteLine("Error exception:" + msg.Message);
if (msg.InnerException != null)
{
Console.WriteLine("Inner exception: {0}", msg.InnerException.Message);
}
Console.WriteLine("Authentication failed - closing the connection.");
sslStream.Close();
client.Close();
return;
}
finally//end and close the connection after finishing the session
{
sslStream.Close();
client.Close();
}
}
/// <summary>
/// Read incoming message from client.
/// </summary>
/// <param name="sslStream">Stream to be read</param>
/// <returns></returns>
static string ReadMessage(SslStream sslStream)
{
//Read Message from client
// The client signals the end of the message using the
// "<EOF>" marker.
byte[] buffer = new byte[2048];
StringBuilder message = new StringBuilder();
int bytes_ = -1;
do
{
// Read the client's test message.
bytes_ = sslStream.Read(buffer, 0, buffer.Length);
// Use Decoder class to convert from bytes to UTF8
// in case a character spans two buffers.
Decoder decoder = Encoding.UTF8.GetDecoder();
char[] chars = new char[decoder.GetCharCount(buffer, 0, bytes_)];
decoder.GetChars(buffer, 0, bytes_, chars, 0);
message.Append(chars);
// Check for EOF or an empty message.
if (message.ToString().IndexOf("<EOF>") != -1)
{
break;
}
} while (bytes_ != 0);
return message.ToString();
}
/// <summary>
/// Display the stream security level strength of the cipher, hash and key exchange, along with the protocol.
/// </summary>
/// <param name="sslStream">Stream to be displayed</param>
static void DisplaySecurityLevel(SslStream sslStream)
{
Console.WriteLine("Cipher: {0} strength {1}", sslStream.CipherAlgorithm, sslStream.CipherStrength);
Console.WriteLine("Hash: {0} strength {1}", sslStream.HashAlgorithm, sslStream.HashStrength);
Console.WriteLine("Key Exchange: {0} strength {1}", sslStream.KeyExchangeAlgorithm, sslStream.KeyExchangeStrength);
Console.WriteLine("Protocol: {0}", sslStream.SslProtocol);
}
/// <summary>
/// Display runned security of the stream.
/// </summary>
/// <param name="sslStream">Stream to be displayed</param>
static void DisplaySecurityService(SslStream sslStream)
{
Console.WriteLine("Is authenticated: {0} server? {1}", sslStream.IsAuthenticated, sslStream.IsServer);
Console.WriteLine("Is Signed: {0}", sslStream.IsSigned);
Console.WriteLine("Is Encrypted: {0}",sslStream.IsEncrypted);
}
/// <summary>
/// Display the certificate information of the stream for both server and client.
/// </summary>
/// <param name="sslStream">Stream to be displayed</param>
static void DisplayCertificateInfo(SslStream sslStream)
{
//Display the properties of server's certificate
Console.WriteLine("Certificate revocation list checked: {0}", sslStream.CheckCertRevocationStatus);
X509Certificate localCertificate = sslStream.LocalCertificate;
if (sslStream.LocalCertificate != null)
{
Console.WriteLine("Local cert is issued to {0}, valid from {1} to {2}.",
localCertificate.Subject,
localCertificate.GetEffectiveDateString(),
localCertificate.GetExpirationDateString());
}
else
{
Console.WriteLine("Local cert is not available");
}
//Display the properties of client's certificate
X509Certificate remoteCertificate = sslStream.RemoteCertificate;
if (sslStream.RemoteCertificate != null)
{
Console.WriteLine("Remote cert is issued to {0}, valid from {1} to {2}.",
remoteCertificate.Subject,
remoteCertificate.GetEffectiveDateString(),
remoteCertificate.GetExpirationDateString());
}
else
{
Console.WriteLine("Remote cert is not available");
}
}
static void DisplaySysProp(SslStream sslStream)
{
Console.WriteLine("Can read: {0}, write {1}", sslStream.CanRead, sslStream.CanWrite);
Console.WriteLine("Can timeout: {0}", sslStream.CanTimeout);
}
private static void DisplayUsage()
{
Console.WriteLine("To start the server specify:");
Console.WriteLine("serverSync certificateFile.pem/cer/crt");
Environment.Exit(1);
}
public static int Main(string[] args)
{
string certificate = null;
string execfileloc = Assembly.GetExecutingAssembly().Location;
certificate = Path.Combine(Path.GetDirectoryName(execfileloc),"server_cert.p12");
Console.WriteLine(certificate);
Console.WriteLine(Dns.GetHostName());
StartServer(certificate);
return 0;
}
}
}
And here is the client:
namespace Client_Server_SSL2.Client
{
class Client
{
public class SslTcpClient
{
private static Hashtable certificateErrors = new Hashtable();
// The following method is invoked by the RemoteCertificateValidationDelegate.
public static bool ValidateServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
X509Store store = new X509Store("Local", StoreLocation.CurrentUser);
if (sslPolicyErrors == SslPolicyErrors.None)
return true;
Console.WriteLine("Certificate error: {0}", sslPolicyErrors);
// Do not allow this client to communicate with unauthenticated servers.
return false;
}
public static void RunClient(string machineName, string serverName)
{
// Create a TCP/IP client socket.
// machineName is the host running the server application.
TcpClient client = new TcpClient(machineName, 20018);
Console.WriteLine("Client connected.");
// Create an SSL stream that will close the client's stream.
SslStream sslStream = new SslStream(
client.GetStream(),
false,
new RemoteCertificateValidationCallback(ValidateServerCertificate),
null
);
// The server name must match the name on the server certificate.
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;
}
// Encode a test message into a byte array.
// Signal the end of the message using the "<EOF>".
byte[] messsage = Encoding.UTF8.GetBytes("Hello from the client.<EOF>");
// Send hello message to the server.
sslStream.Write(messsage);
sslStream.Flush();
// Read message from the server.
string serverMessage = ReadMessage(sslStream);
Console.WriteLine("Server says: {0}", serverMessage);
// Close the client connection.
client.Close();
Console.WriteLine("Client closed.");
}
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);
// Use Decoder class to convert from bytes to UTF8
// in case a character spans two buffers.
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();
}
private static void DisplayUsage()
{
Console.WriteLine("To start the client specify:");
Console.WriteLine("clientSync machineName [serverName]");
Environment.Exit(1);
}
public static int Main(string[] args)
{
string serverCertificateName = null;
string machineName = null;
if (args == null || args.Length < 1)
{
DisplayUsage();
}
// User can specify the machine name and server name.
// Server name must match the name on the server's certificate.
machineName = args[0];
if (args.Length < 2)
{
serverCertificateName = machineName;
}
else
{
serverCertificateName = args[1];
}
SslTcpClient.RunClient(machineName, serverCertificateName);
Console.ReadKey();
return 0;
}
}
}
}
This code is modified from the msdn guide.
Edit: here is the error I got: System.ComponentModel.Win32Exception (0x80090325): The certificate chain was issued by an authority that is not trusted.