I'm attempting to write a .Net Framework 4.7.2 client that can communicate with a proto buffer payload eventually, over SSL. I think the best approach would be a TAP model, but I've had less luck finding a working example to repeatedly do the send (if data to send) and process server messages as and when the server replies, and therefore I've gone with the APM approach here below.
My problem is I get a "The BeginWrite method cannot be called when another write is pending" error in SendWorker(). My server does receive the connection and the message. I can work on the SSL cert later.
My output is:
Client connected.
Client is selecting a local certificate.
To quit, press the enter key.
Client is selecting a local certificate.
Validating the server certificate.
Certificate error: RemoteCertificateNameMismatch
Writing data to the server.
Writing data to the server.
The test code is:
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
namespace SslClient
{
// This is a client side of a
// client-server application that communicates using the
// SslStream and TcpClient classes.
// After connecting to the server and authenticating,
// the client should repeatedly send items from a q and listen for any replies
public class SslTcpClient
{
// complete is used to terminate the application when all
// asynchronous calls have completed or any call has thrown an exception.
// complete might be used by any of the callback methods.
private bool complete = false;
// e stores any exception thrown by an asynchronous method so that
// the mail application thread can display the exception and terminate gracefully.
// e might be used by any of the callback methods.
private Exception e = null;
// readData and buffer holds the data read from the server.
// They is used by the ReadCallback method.
private StringBuilder readData = new StringBuilder();
private byte[] buffer = new byte[2048];
EventWaitHandle _trigger = new AutoResetEvent(false);
private TcpClient _tcpClient;
private SslStream _sslStream;
private Thread _senderThread;
private readonly object _locker = new object();
public ConcurrentQueue<byte[]> WriteQueue = new ConcurrentQueue<byte[]>();
// The following method is invoked by the CertificateValidationDelegate.
public bool ValidateServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
Console.WriteLine("Validating the server certificate.");
if (sslPolicyErrors == SslPolicyErrors.None)
return true;
Console.WriteLine("Certificate error: {0}", sslPolicyErrors);
// Do not allow this client to communicate with unauthenticated servers.
return true; // ToDo
}
public X509Certificate SelectLocalCertificate(
object sender,
string targetHost,
X509CertificateCollection localCertificates,
X509Certificate remoteCertificate,
string[] acceptableIssuers)
{
Console.WriteLine("Client is selecting a local certificate.");
if (acceptableIssuers != null &&
acceptableIssuers.Length > 0 &&
localCertificates != null &&
localCertificates.Count > 0)
{
// Use the first certificate that is from an acceptable issuer.
foreach (X509Certificate certificate in localCertificates)
{
string issuer = certificate.Issuer;
if (Array.IndexOf(acceptableIssuers, issuer) != -1)
return certificate;
}
}
if (localCertificates != null &&
localCertificates.Count > 0)
return localCertificates[0];
return null;
}
private void AuthenticateCallback(IAsyncResult ar)
{
SslStream stream = (SslStream)ar.AsyncState;
try
{
stream.EndAuthenticateAsClient(ar);
}
catch (Exception authenticationException)
{
e = authenticationException;
//complete = true;
return;
}
}
public void ProduceConsumerQueue(TcpClient tcpClient, SslStream sslStream)
{
_tcpClient = tcpClient;
_sslStream = sslStream;
_senderThread = new Thread(SenderWorker);
_senderThread.Start();
}
public void Send(byte[] payload)
{
WriteQueue.Enqueue(payload);
_trigger.Set();
}
//our workhorse. This will just sit and wait until there is something to send
//it will continue to send until there is nothing left
void SenderWorker()
{
while (!complete)
{
string msg = null;
if (WriteQueue.Count > 0)
{
byte[] message;
WriteQueue.TryDequeue(out message);
if (_tcpClient.Connected && message.Length > 0)
{
if (_sslStream.CanWrite)
{
_sslStream.BeginWrite(message, 0, message.Length,
new AsyncCallback(WriteCallback),
_sslStream);
}
}
}
else
{
_trigger.WaitOne(); // Zero msgs left.. just wait
}
}
}
private void WriteCallback(IAsyncResult ar)
{
SslStream stream = (SslStream)ar.AsyncState;
try
{
Console.WriteLine("Writing data to the server.");
stream.EndWrite(ar);
// Asynchronously read a message from the server.
//stream.BeginRead(buffer, 0, buffer.Length,
// new AsyncCallback(ReadCallback),
// stream);
}
catch (Exception writeException)
{
e = writeException;
complete = true;
return;
}
}
private void ReadCallback(IAsyncResult ar)
{
// Read the message sent by the server.
// The end of the message is signaled using the
// "<EOF>" marker.
SslStream stream = (SslStream)ar.AsyncState;
int byteCount = -1;
try
{
Console.WriteLine("Reading data from the server.");
byteCount = stream.EndRead(ar);
// 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, byteCount)];
decoder.GetChars(buffer, 0, byteCount, chars, 0);
readData.Append(chars);
// Check for EOF or an empty message.
if (byteCount != 0)
{
// We are not finished reading.
// Asynchronously read more message data from the server.
stream.BeginRead(buffer, 0, buffer.Length,
new AsyncCallback(ReadCallback),
stream);
}
else
{
Console.WriteLine("Message from the server: {0}", readData.ToString());
}
if (WriteQueue.Count>0)
{
//byte[] message;
//WriteQueue.TryDequeue(out message);
//if (message.Length > 0)
//{
// stream.BeginWrite(message, 0, message.Length,
// new AsyncCallback(WriteCallback),
// ar);
//}
}
}
catch (Exception readException)
{
e = readException;
complete = true;
return;
}
complete = true;
}
public int Main(string serverName, int port)
{
// Create a TCP/IP client socket.
TcpClient client = new TcpClient(serverName, port);
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),
new LocalCertificateSelectionCallback(SelectLocalCertificate)
);
var clientCertificate = FindClientCertificate(X509FindType.FindBySubjectName, "IIS Express Development Certificate"); // IIS Express Development Certificate
var clientCertificates = new X509CertificateCollection(new X509Certificate[] { clientCertificate });
// Begin authentication.
// The server name must match the name on the server certificate.
sslStream.BeginAuthenticateAsClient(
"UIClient",
clientCertificates,
SslProtocols.Tls12,
false,/* cert revocation */
new AsyncCallback(AuthenticateCallback),
sslStream);
// sslStream.AuthenticateAsClient("UIClient", clientCertificates, SslProtocols.Tls12, false);
ProduceConsumerQueue(client, sslStream);
// User can press a key to exit application, or let the
// asynchronous calls continue until they complete.
Console.WriteLine("To quit, press the enter key.");
do
{
// Real world applications would do work here
// while waiting for the asynchronous calls to complete.
Send(System.Text.Encoding.UTF8.GetBytes("hello".ToArray()));
System.Threading.Thread.Sleep(100);
} while (complete != true && Console.KeyAvailable == false);
if (Console.KeyAvailable)
{
Console.ReadLine();
Console.WriteLine("Quitting.");
client.Close();
sslStream.Close();
return 1;
}
if (e != null)
{
Console.WriteLine("An exception was thrown: {0}", e.ToString());
}
sslStream.Close();
client.Close();
Console.WriteLine("Good bye.");
return 0;
}
private static X509Certificate FindClientCertificate(X509FindType type, string item)
{
return Find(type, item, StoreLocation.CurrentUser) ?? Find(type, item, StoreLocation.LocalMachine);
}
private static X509Certificate Find(X509FindType t, string item, StoreLocation location)
{
using (var store = new X509Store(location))
{
store.Open(OpenFlags.OpenExistingOnly);
var certs = store.Certificates.Find(t, item, true);
if (t == X509FindType.FindBySubjectName)
{
return store.Certificates.OfType<X509Certificate2>().FirstOrDefault(x => x.FriendlyName == item);
}
return certs.OfType<X509Certificate>().FirstOrDefault();
}
}
}
}
How can it be fixed? Is there a better complete solution that you can share?
Thanks in advance,
Steve
Related
In my application I am using the below code to validate the client certificate
public static async Task<string> CallApi(string url, Context context)
{
var hostName = "mytestapp.azurewebsites.net";
var port = 443;
Stream keyin = Application.Context.Assets.Open("Server.pfx");
var password = "pass123";
using (MemoryStream memStream = new MemoryStream())
{
keyin.CopyTo(memStream);
var certificates = new X509Certificate2Collection(new X509Certificate2(memStream.ToArray(), password));
await Task.Run(() =>
{
// Create a TCP/IP client socket.
// machineName is the host running the server application.
TcpClient client = new TcpClient(hostName, port);
Console.WriteLine("Client connected.");
// Create an SSL stream that will close the client's stream.
SslStream sslStream = new SslStream(
client.GetStream(),
false,
ValidateServerCertificate);
// The server name must match the name on the server certificate.
try
{
sslStream.AuthenticateAsClient(hostName, certificates, SslProtocols.Tls12, true);
}
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;
}
});
}
return string.Empty;
}
after successful authentication, I would like to make and HTTP get request.
sslStream.AuthenticateAsClient(hostName, certificates, SslProtocols.Tls12, true);
After this statement. Say for example I need to call below http Get call
https://mytestapp.azurewebsites.net/api/GetUserProfile?userId="Sooraj"
How I can invoke this call? Or is it possible to implement the same?
Please help
Try something like this,
try
{
sslStream.AuthenticateAsClient(hostName, certificates, SslProtocols.Tls12, true);
byte[] buffer = new byte[5120];
int bytes;
var pqr = string.Format("GET {0} HTTP/1.1\r\nHost: {1}\r\n\r\n", url, "mytestapp.azurewebsites.net");
byte[] request = Encoding.UTF8.GetBytes(pqr);
sslStream.Write(request, 0, request.Length);
var ppp = ReadStream(sslStream);
sslStream.Flush();
}
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;
}
private static string ReadStream(Stream stream)
{
byte[] resultBuffer = new byte[2048];
string value = "";
//requestStream.BeginRead(resultBuffer, 0, resultBuffer.Length, new AsyncCallback(ReadAsyncCallback), new result() { buffer = resultBuffer, stream = requestStream, handler = callback, asyncResult = null });
do
{
try
{
int read = stream.Read(resultBuffer, 0, resultBuffer.Length);
value += Encoding.UTF8.GetString(resultBuffer, 0, read);
if (read < resultBuffer.Length)
break;
}
catch { break; }
} while (true);
return value;
}
It will give you the response with all set of data. and later we need to parse the required information.
i am fairly new to Network Programming and i need help with connecting a TCP Client via TLS. I was given a project that was coded already and it came with both the certificate and the public key provided. I have installed the pfx certificate on my local machine and have coded a new TCP Listener/Server and TCP Client pointing to local host as below:
The Client
static void Main(string[] args)
{
string server = "localhost";
TcpClient client = new TcpClient(server, 5997);
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;
//client.Connect(server, 5997);//connection without TLS - authenticating properly
using (SslStream sslStream = new SslStream(client.GetStream(), false,
new RemoteCertificateValidationCallback(ValidateServerCertificate), null))
{
try
{
var servername = "myAuthenticatingServerName";//the server name must be the same as the one on the server certificate
sslStream.AuthenticateAsClient(servername);
try
{
Console.WriteLine("Client Connected...");
// 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.");
}
catch (Exception e)
{
Console.WriteLine("Error..... " + e.StackTrace);
}
}
catch (AuthenticationException e)
{
Console.WriteLine($"Error: { e.Message}");
if (e.InnerException != null)
{
Console.WriteLine($"Inner exception: {e.InnerException.Message}");
}
Console.WriteLine("Authentication failed - closing the connection.");
client.Close();
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
Console.WriteLine("Authentication failed - closing the connection.");
client.Close();
}
}
client.Close();
Console.WriteLine($"Connection closed at {DateTime.Now}.");
Console.ReadLine();
}
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
{
try
{
bytes = sslStream.Read(buffer, 0, buffer.Length);
}
catch (Exception ex)
{
throw ex;
}
// 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();
}
public static bool ValidateServerCertificate(object sender, X509Certificate certificate,
X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None)
return true;
Console.WriteLine($"Certificate error: {sslPolicyErrors}");
// Do not allow this client to communicate with unauthenticated servers.
return false;
}
The Server
public sealed class SslTcpServer
{
static X509Certificate2 serverCertificate = null;
// The certificate parameter specifies the name of the file
// containing the machine certificate.
public static void RunServer(X509Certificate2 certificate)
{
serverCertificate = certificate;
// Create a TCP/IP (IPv4) socket and listen for incoming connections.
TcpListener listener = new TcpListener(IPAddress.Any, 8080);
listener.Start();
while (true)
{
Console.WriteLine("Waiting for a client to connect...");
// Application blocks while waiting for an incoming connection.
// Type CNTL-C to terminate the server.
TcpClient client = listener.AcceptTcpClient();
ProcessClient(client);
}
}
static void ProcessClient(TcpClient client)
{
// A client has connected. Create the
// SslStream using the client's network stream.
SslStream sslStream = new SslStream(
client.GetStream(), false);
// Authenticate the server but don't require the client to authenticate.
try
{
sslStream.AuthenticateAsServer(serverCertificate, clientCertificateRequired: false, enabledSslProtocols: SslProtocols.Tls, checkCertificateRevocation: true);
// Display the properties and settings for the authenticated stream.
DisplaySecurityLevel(sslStream);
DisplaySecurityServices(sslStream);
DisplayCertificateInformation(sslStream);
DisplayStreamProperties(sslStream);
// Set timeouts for the read and write to 5 seconds.
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.");
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
{
// The client stream will be closed with the sslStream
// because we specified this behavior when creating
// the sslStream.
sslStream.Close();
client.Close();
}
}
static string ReadMessage(SslStream sslStream)
{
// Read the message sent by the client.
// The client signals the end of the message using the
// "<EOF>" marker.
byte[] buffer = new byte[2048];
StringBuilder messageData = 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);
messageData.Append(chars);
// Check for EOF or an empty message.
if (messageData.ToString().IndexOf("<EOF>") != -1)
{
break;
}
} while (bytes != 0);
return messageData.ToString();
}
static void DisplaySecurityLevel(SslStream stream)
{
Console.WriteLine("Cipher: {0} strength {1}", stream.CipherAlgorithm, stream.CipherStrength);
Console.WriteLine("Hash: {0} strength {1}", stream.HashAlgorithm, stream.HashStrength);
Console.WriteLine("Key exchange: {0} strength {1}", stream.KeyExchangeAlgorithm, stream.KeyExchangeStrength);
Console.WriteLine("Protocol: {0}", stream.SslProtocol);
}
static void DisplaySecurityServices(SslStream stream)
{
Console.WriteLine("Is authenticated: {0} as server? {1}", stream.IsAuthenticated, stream.IsServer);
Console.WriteLine("IsSigned: {0}", stream.IsSigned);
Console.WriteLine("Is Encrypted: {0}", stream.IsEncrypted);
}
static void DisplayStreamProperties(SslStream stream)
{
Console.WriteLine("Can read: {0}, write {1}", stream.CanRead, stream.CanWrite);
Console.WriteLine("Can timeout: {0}", stream.CanTimeout);
}
static void DisplayCertificateInformation(SslStream stream)
{
Console.WriteLine("Certificate revocation list checked: {0}", stream.CheckCertRevocationStatus);
X509Certificate localCertificate = stream.LocalCertificate;
if (stream.LocalCertificate != null)
{
Console.WriteLine("Local cert was issued to {0} and is valid from {1} until {2}.",
localCertificate.Subject,
localCertificate.GetEffectiveDateString(),
localCertificate.GetExpirationDateString());
}
else
{
Console.WriteLine("Local certificate is null.");
}
// Display the properties of the client's certificate.
X509Certificate remoteCertificate = stream.RemoteCertificate;
if (stream.RemoteCertificate != null)
{
Console.WriteLine("Remote cert was issued to {0} and is valid from {1} until {2}.",
remoteCertificate.Subject,
remoteCertificate.GetEffectiveDateString(),
remoteCertificate.GetExpirationDateString());
}
else
{
Console.WriteLine("Remote certificate is null.");
}
}
public static int Main(string[] args)
{
RunServer(Certificate.GetCertificate());
return 0;
}
}
public class Certificate
{
public static X509Certificate2 GetCertificate()
{
string certificatePath = ConfigurationManager.AppSettings["certificatePath"].ToString();
var stream = File.OpenRead(certificatePath);
return new X509Certificate2(ReadStream(stream), "mypassword");
}
private static byte[] ReadStream(Stream input)
{
byte[] buffer = new byte[16 * 1024];
using (MemoryStream ms = new MemoryStream())
{
int read;
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
{
ms.Write(buffer, 0, read);
}
return ms.ToArray();
}
}
}
My problem is that the client runs properly and even Authenticates on
sslStream.AuthenticateAsClient(servername);
but the server does not pick up any connection on
TcpClient client = listener.AcceptTcpClient();
ProcessClient(client);
Am i missing something, or doing something wrong? Also, is it worth starting afresh, getting a new certificate etc?
On the server-side you are listening to port 8080 and on the client-side your are connecting to port 5997. To be more specific your server is hosting [YourIP]:8080 and your client is trying to connect to [YourIP]:5997
Moreover Tls and SSL are usually used on the port 443. The client and server ports need to be the same so they can connect to each other.
I’m also not sure if c# is recognising ‘localhost’ as ‘YourIP’ its better to open up your cmd(in case you are using windows) Type in ‘ipconfig’ hit enter and lookup and use your IPv4 Address.
Here is my code:
public class ServerClient
{
public TcpClient tcp;
public StreamReader streamReader;
public StreamWriter streamWriter;
public int accountId;
public int connectionId;
public ServerClient(TcpClient clientSocket)
{
tcp = clientSocket;
streamReader = new StreamReader(tcp.GetStream(), false);
streamWriter = new StreamWriter(tcp.GetStream());
clientSocket.NoDelay = true;
}
}
Here is how i am listening for data, this is called in a while loop:
// Check for message from Client.
NetworkStream s = c.tcp.GetStream();
if (s.DataAvailable)
{
string data = c.streamReader.ReadLine();
if (data != null)
{
if (ValidateJSON(data))
{
OnIncomingData(c, data);
}
}
}
//continue;
Here is how i check if the client is still connected:
private bool IsConnected(TcpClient c)
{
try
{
if (c != null && c.Client != null && c.Client.Connected)
{
if (c.Client.Poll(0, SelectMode.SelectRead))
{
return !(c.Client.Receive(new byte[1], SocketFlags.Peek) == 0);
}
return true;
}
else
{
return false;
}
}
catch
{
return false;
}
}
Here is how i send data into the stream:
string JSonData = JsonConvert.SerializeObject(SendData);
c.streamWriter.WriteLine(JSonData);
c.streamWriter.Flush();
Here is my client how it listens for message:
public void ConnectToWorldServer()
{
if (socketReady)
{
return;
}
//Default host and port values;
string host = "127.0.0.1";
int port = 8080;
//ClientLoginServer ClientLoginServer = new ClientLoginServer();
try
{
socket = new TcpClient(host, port);
stream = socket.GetStream();
socket.NoDelay = true;
writer = new StreamWriter(stream);
reader = new StreamReader(stream);
socketReady = true;
//Preserve the connection to worldserver thrue scenes
UnityThread.executeInUpdate(() =>
{
DontDestroyOnLoad(worldserverConnection);
});
// Start listening for connections.
while (true)
{
keepReading = true;
while (keepReading)
{
keepReading = true;
if (socketReady)
{
if (stream.DataAvailable)
{
string data = reader.ReadLine();
if (data != null)
{
UnityThread.executeInUpdate(() =>
{
OnIncomingData(data);
});
}
}
}
System.Threading.Thread.Sleep(1);
}
System.Threading.Thread.Sleep(1);
}
}
catch (Exception e)
{
Debug.Log("Socket error : " + e.Message);
}
}
I have red about SSL Stream. Here is what i have found: https://msdn.microsoft.com/en-us/library/system.net.security.sslstream(v=vs.110).aspx
I have several questions:
Do i need to buy an SSL certificate like a certificate for a website ?
Can i generate a self-signed SSL certificate? If so and it's needed can you show me any reference of how to achieve that ?
Is it possible to attach this SSL Stream to my StreamReader/StreamWriter ? Is it even needed or i have to remake the whole connection/communication thing?
When SSLStream is created the whole communication between the server and the client will be encrypted so anybody can't stay in between and listen/understand what they are communicating ?
If i have to attach a certificate file to the client so it may establish a successful SSL connection to the server and then send that client to all the people which have to use that client, doesn't that compromise the security of the SSL connection as i am sending them the certificate as well ?
This is all i wanted to ask. The best thing you can do to help me with your answer is if possible please put the code changes i have to apply in order to make that StreamReader/StreamWriter work over encrypted SSL.
you need a certificate; makecert would work, but it won't be trusted by default, so you'd either need to supply a custom RemoteCertificateValidationCallback to the consumer, or use a trusted cert; Let's Encrypt might be useful there
makecert or New-SelfSignedCertificate
the SslStream would need to go between the NetworkStream and the StreamReader; frankly, it isn't clear to make that StreamReader give you much here - you could do everything here with just the Streaam API; so instead of WriteLine, you'd encode manually and then Write the encoded buffer; but - there's no reason that you can't just whack SslStream in the middle of the two
yes
only the server needs the certificate (and in particular, only the server needs the private key of the certificate); there is something related called "client certificates" used for authenticating the client, but that is a related but different topic
As an example ripped from SE.Redis and mangled beyond legibility; SE.Redis optionally encrypts, and is basically:
Stream stream = // connect (get a NetworkStream)
if (config.Ssl) {
var ssl = new SslStream(stream, ... cert check callbacks, etc ...);
ssl.AuthenticateAsClient(expectedHost, allowedSslProtocols);
stream = ssl;
}
so it you had something like that, you'd just make sure you attached any StreamReader/StreamWriter after you've wrapped the NetworkStream in SslStream. But again: I really think you could remove StreamReader/StreamWriter here without much inconvenience.
I am new in C# development. I am trying to use ssl/tls over tcp but in my code, system.net.sockets.socket (bare socket) is used not tcpclient or tcplistner. I have searched over net atleast 200 links but I didn't get anything related that. I want to use less coding and done ssl or tsll over tcp socket connection. I have client, server, ca certificate, key in .key format. Please help with example or link. You can ask questions if u feel more details.
Why don't you want to use TcpClient? Creating a SSL connection with TcpClient and Ssltream is quite easy. Unless you require thousands of simultaneous connections I would stick with TcpClient and SSLStream.
A basic TcpClient and SslStream example would be as follows:
static void Main(string[] args)
{
string server = "127.0.0.1";
TcpClient client = new TcpClient(server, 443);
using (SslStream sslStream = new SslStream(client.GetStream(), false,
new RemoteCertificateValidationCallback(ValidateServerCertificate), null))
{
sslStream.AuthenticateAsClient(server);
// This is where you read and send data
}
client.Close();
}
public static bool ValidateServerCertificate(object sender, X509Certificate certificate,
X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
return true;
}
High performance socket code can be difficult to write in .NET but there are good examples out there. You have a number of choices. I'm not sure one solution fits all so I'll list a few here.
Using an [Asynchronous Client Socket] maybe be a good place to start
There are a few existing libraries you can make use of. I've used Nito/Async however I don't think it has been updated since 2009. There was talk of a version 2 but I don't believe it materialized.
I'm not familar with it but CodeProject has C# SocketAsyncEventArgs High Performance Socket Code
Review Microsoft's guidance, High Performance .NET Socket Server Using Async Winsock
Read everything that Stephen Toub has to say including Awaiting Socket Operations
I didn't address SSL specifically but look into the SslStream Class.
You'll also want to look into buffer pooling. If you're serving thousands of clients garbage collection will be a problem. An excellent introduction to this is Sunny Ahuwanya's Blog
https://github.com/StephenCleary/AsyncEx
before.
You should be able to use System.Net.Sockets.NetworkStream to wrap your socket and then System.Net.Security.SslStream to wrap the network stream.
Socket socket;
Stream networkStream = new NetworkStream(socket);
Stream sslStream = new SslStream(networkStream);
this is for you
public class SSL
{
private bool CertificateValidationCallback(object sender, System.Security.Cryptography.X509Certificates.X509Certificate certificate, System.Security.Cryptography.X509Certificates.X509Chain chain, System.Net.Security.SslPolicyErrors sslPolicyErrors)
{
return true;
}
public class Certificate
{
public System.Security.Cryptography.X509Certificates.X509Certificate2 GetCertificate()
{
string certificatePath = #"D:\Rayan Ab Niro\Projecr\VPN\WindowsVPN\SSL.pfx"; //ConfigurationManager.AppSettings["certificatePath"].ToString();
var stream = File.OpenRead(certificatePath);
return new System.Security.Cryptography.X509Certificates.X509Certificate2(ReadStream(stream), "mypassword", System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.MachineKeySet | System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.Exportable);
}
private byte[] ReadStream(Stream input)
{
byte[] buffer = new byte[16 * 1024];
using (MemoryStream ms = new MemoryStream())
{
int read;
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
{
ms.Write(buffer, 0, read);
}
return ms.ToArray();
}
}
}
public System.Net.Security.SslStream GetStream(NetworkStream _NetworkStream)
{
try
{
System.Net.Security.SslStream sslStream = new System.Net.Security.SslStream(_NetworkStream, false,);
sslStream.AuthenticateAsServer(serverCertificate, clientCertificateRequired: false,
enabledSslProtocols:
System.Security.Authentication.SslProtocols.Default |
System.Security.Authentication.SslProtocols.None |
System.Security.Authentication.SslProtocols.Tls |
System.Security.Authentication.SslProtocols.Tls11 |
System.Security.Authentication.SslProtocols.Tls12 |
System.Security.Authentication.SslProtocols.Ssl2 |
System.Security.Authentication.SslProtocols.Ssl3
, checkCertificateRevocation: true);
new SSL().DisplaySecurityLevel(sslStream);
new SSL().DisplaySecurityServices(sslStream);
new SSL().DisplayCertificateInformation(sslStream);
new SSL().DisplayStreamProperties(sslStream);
sslStream.ReadTimeout = 5000;
sslStream.WriteTimeout = 5000;
return sslStream;
}
catch (Exception ex)
{
throw ex;
}
}
public string ReadMessage(System.Net.Security.SslStream sslStream)
{
// Read the message sent by the client.
// The client signals the end of the message using the
// "<EOF>" marker.
byte[] buffer = new byte[2048];
StringBuilder messageData = 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);
messageData.Append(chars);
// Check for EOF or an empty message.
if (messageData.ToString().IndexOf("<EOF>") != -1)
{
//break;
}
//} while (bytes != 0);
return messageData.ToString();
}
public void DisplaySecurityLevel(System.Net.Security.SslStream stream)
{
Console.WriteLine("Cipher: {0} strength {1}", stream.CipherAlgorithm, stream.CipherStrength);
Console.WriteLine("Hash: {0} strength {1}", stream.HashAlgorithm, stream.HashStrength);
Console.WriteLine("Key exchange: {0} strength {1}", stream.KeyExchangeAlgorithm, stream.KeyExchangeStrength);
Console.WriteLine("Protocol: {0}", stream.SslProtocol);
}
public void DisplaySecurityServices(System.Net.Security.SslStream stream)
{
Console.WriteLine("Is authenticated: {0} as server? {1}", stream.IsAuthenticated, stream.IsServer);
Console.WriteLine("IsSigned: {0}", stream.IsSigned);
Console.WriteLine("Is Encrypted: {0}", stream.IsEncrypted);
}
public void DisplayStreamProperties(System.Net.Security.SslStream stream)
{
Console.WriteLine("Can read: {0}, write {1}", stream.CanRead, stream.CanWrite);
Console.WriteLine("Can timeout: {0}", stream.CanTimeout);
}
public void DisplayCertificateInformation(System.Net.Security.SslStream stream)
{
Console.WriteLine("Certificate revocation list checked: {0}", stream.CheckCertRevocationStatus);
System.Security.Cryptography.X509Certificates.X509Certificate localCertificate = stream.LocalCertificate;
if (stream.LocalCertificate != null)
{
Console.WriteLine("Local cert was issued to {0} and is valid from {1} until {2}.",
localCertificate.Subject,
localCertificate.GetEffectiveDateString(),
localCertificate.GetExpirationDateString());
}
else
{
Console.WriteLine("Local certificate is null.");
}
// Display the properties of the client's certificate.
System.Security.Cryptography.X509Certificates.X509Certificate remoteCertificate = stream.RemoteCertificate;
if (stream.RemoteCertificate != null)
{
Console.WriteLine("Remote cert was issued to {0} and is valid from {1} until {2}.",
remoteCertificate.Subject,
remoteCertificate.GetEffectiveDateString(),
remoteCertificate.GetExpirationDateString());
}
else
{
Console.WriteLine("Remote certificate is null.");
}
}
}
I tried to implement a feed back service code which i found over here.
here is my code:
private X509Certificate getServerCert(string certName)
{
var store = new X509Store(StoreLocation.LocalMachine);
try
{
store.Open(OpenFlags.ReadOnly);
var certs = store.Certificates.Find(X509FindType.FindBySubjectName, certName, false);
return certs.Count == 0 ? null : certs[0];
}
finally
{
store.Close();
}
}
public String CheckFeedbackService(string certaName)
{
System.Net.ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(ValidateServerCertificate);
KIREIP.Core.Manager.IphoneErrorLog errorLog = new KIREIP.Core.Manager.IphoneErrorLog();
// Create an empty collection of certs
X509Certificate2Collection certs = new X509Certificate2Collection();
// Add the Apple cert to our collection
certs.Add(getServerCert(certaName));
// Apple feedback server address
string apsHostF;
if (getServerCert(certaName).ToString().Contains("Production"))
apsHostF = "feedback.push.apple.com";
else
apsHostF = "feedback.sandbox.push.apple.com";
// Create a TCP socket connection to the Apple server on port 2196
TcpClient tcpClientF = new TcpClient(apsHostF, 2196);
//Set up
byte[] buffer = new byte[38];
int recd = 0;
DateTime minTimestamp = DateTime.Now.AddYears(-1);
string result = string.Empty;
// Create a new SSL stream over the connection
SslStream sslStreamF = new SslStream(tcpClientF.GetStream(), true, new RemoteCertificateValidationCallback(ValidateServerCertificate));
try
{
// Authenticate using the Apple cert
sslStreamF.AuthenticateAsClient(apsHostF, certs, SslProtocols.Default, false);
//TODO: Read in data and remove device tokens if any found.
errorLog.AddErrorLog("Stream Readable ::" + sslStreamF.CanRead);
//errorLog.AddErrorLog("Host Name ::" + hostName);
errorLog.AddErrorLog("Cert Name ::" + certs[0].FriendlyName);
if (sslStreamF != null)
{
errorLog.AddErrorLog("Connection Started");
//Get the first feedback
recd = sslStreamF.Read(buffer, 0, buffer.Length);
errorLog.AddErrorLog("Buffer length ::" + recd);
//Continue while we have results and are not disposing
while (recd > 0)
{
errorLog.AddErrorLog("Reading Started");
//Get our seconds since 1970 ?
byte[] bSeconds = new byte[4];
byte[] bDeviceToken = new byte[32];
Array.Copy(buffer, 0, bSeconds, 0, 4);
//Check endianness
if (BitConverter.IsLittleEndian)
Array.Reverse(bSeconds);
int tSeconds = BitConverter.ToInt32(bSeconds, 0);
//Add seconds since 1970 to that date, in UTC and then get it locally
var Timestamp = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(tSeconds).ToLocalTime();
//Now copy out the device token
Array.Copy(buffer, 6, bDeviceToken, 0, 32);
string deviceToken = BitConverter.ToString(bDeviceToken).Replace("-", "").ToLower().Trim();
//Make sure we have a good feedback tuple
if (deviceToken.Length == 64 && Timestamp > minTimestamp)
{
errorLog.AddErrorLog("Feedback " + deviceToken);
result = deviceToken;
errorLog.AddErrorLog(result);
}
//Clear array to reuse it
Array.Clear(buffer, 0, buffer.Length);
//Read the next feedback
recd = sslStreamF.Read(buffer, 0, buffer.Length);
}
errorLog.AddErrorLog("Reading Ended");
}
if (sslStreamF != null)
sslStreamF.Close();
if (tcpClientF != null)
tcpClientF.Close();
}
catch (AuthenticationException e)
{
errorLog.AddErrorLog("Authentication failed - closing the connection.");
sslStreamF.Close();
tcpClientF.Close();
return "NOAUTH";
}
finally
{
// The client stream will be closed with the sslStream
// because we specified this behavior when creating
// the sslStream.
sslStreamF.Close();
tcpClientF.Close();
}
return "";
}
Whenever i run this code i get null value from getServerCert(certName) method, because of this null value my further execution get failed.
Any help would be greatly appreciated
I have used JdSoft.Apns.Feedback.dll that I found here (or you may google it directly to get latest version).
Using this .dll I have generate the below method with 2 delegates
public string CheckFeedBack()
{
//Variables you may need to edit:
//---------------------------------
//Log.AddErrorLog("Start");
//True if you are using sandbox certificate, or false if using production
bool sandbox = Convert.ToBoolean(System.Configuration.ConfigurationManager.AppSettings["sandbox"].ToString());
//Put your PKCS12 .p12 or .pfx filename here.
// Assumes it is in the same directory as your app
string p12File = System.Configuration.ConfigurationManager.AppSettings["p12File"].ToString();
//This is the password that you protected your p12File
// If you did not use a password, set it as null or an empty string
string p12FilePassword = System.Configuration.ConfigurationManager.AppSettings["p12FilePassword"].ToString();
//Actual Code starts below:
//--------------------------------
//Get the filename assuming the file is in the same directory as this app
string p12Filename = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, p12File);
//errorLog.AddErrorLog(p12Filename);
//Create the feedback service consumer
FeedbackService service = new FeedbackService(sandbox, p12Filename, p12FilePassword);
//Wireup the events
service.Error += new FeedbackService.OnError(service_Error);
service.Feedback += new FeedbackService.OnFeedback(service_Feedback);
//Run it. This actually connects and receives the feedback
// the Feedback event will fire for each feedback object
// received from the server
service.Run();
Log.AddErrorLog("Done.");
Log.AddErrorLog("Cleaning up...");
//Clean up
service.Dispose();
return "Checked database table log..";
}
Delegates to wireup feedback and error events
static void service_Feedback(object sender, Feedback feedback)
{
Log.AddErrorLog(string.Format("Feedback - Timestamp: {0} - DeviceId: {1}", feedback.Timestamp, feedback.DeviceToken));
//DeviceToken retrive over here
}
static void service_Error(object sender, Exception ex)
{
Log.AddErrorLog(string.Format("Error: {0}", ex.Message));
//Error Message if failed
}