Note: If you want to experience a complete C++ .NET/C++-CLI programming tutorial please jump to Visual C++ .NET programming tutorial.
Create a new CLR console application project and you might want to use SslStreamClientCP as the project and solution names.
Add the following code.
// SslStreamClientCP.cpp : main project file.
#include "stdafx.h"
using namespace System; using namespace System::Collections; using namespace System::Globalization; using namespace System::Net; using namespace System::Net::Security; using namespace System::Net::Sockets; using namespace System::Security::Authentication; using namespace System::Text; using namespace System::Security::Cryptography::X509Certificates; using namespace System::IO;
public ref class SslTcpClient { private: static Hashtable^ certificateErrors = gcnew Hashtable; // Load a table of errors that might cause the certificate authentication to fail. static void InitializeCertificateErrors() { certificateErrors->Add(0x800B0101, "The certification has expired."); certificateErrors->Add(0x800B0104, "A path length constraint in the certification chain has been violated."); certificateErrors->Add(0x800B0105, "A certificate contains an unknown extension that is marked critical."); certificateErrors->Add(0x800B0107, "A parent of a given certificate in fact did not issue that child certificate."); certificateErrors->Add(0x800B0108, "A certificate is missing or has an empty value for a necessary field."); certificateErrors->Add(0x800B0109, "The certificate root is not trusted."); certificateErrors->Add(0x800B010C, "The certificate has been revoked."); certificateErrors->Add(0x800B010F, "The name in the certificate does not not match the host name requested by the client."); certificateErrors->Add(0x800B0111, "The certificate was explicitly marked as untrusted by the user."); certificateErrors->Add(0x800B0112, "A certification chain processed correctly, but one of the CA certificates is not trusted."); certificateErrors->Add(0x800B0113, "The certificate has an invalid policy."); certificateErrors->Add(0x800B0114, "The certificate name is either not in the permitted list or is explicitly excluded."); certificateErrors->Add(0x80092012, "The revocation function was unable to check revocation for the certificate."); certificateErrors->Add(0x80090327, "An unknown error occurred while processing the certificate."); certificateErrors->Add(0x80096001, "A system-level error occurred while verifying trust."); certificateErrors->Add(0x80096002, "The certificate for the signer of the message is invalid or not found."); certificateErrors->Add(0x80096003, "One of the counter signatures was invalid."); certificateErrors->Add(0x80096004, "The signature of the certificate cannot be verified."); certificateErrors->Add(0x80096005, "The time stamp signature or certificate could not be verified or is malformed."); certificateErrors->Add(0x80096010, "The digital signature of the object was not verified."); certificateErrors->Add(0x80096019, "The basic constraint extension of a certificate " "has not been observed."); }
static String^ CertificateErrorDescription(UInt32 problem) { // Initialize the error message dictionary if it is not yet available. Console::WriteLine("Initializing error message dictionary..."); if (certificateErrors->Count == 0) { InitializeCertificateErrors(); }
String^ description = safe_cast<String^>(certificateErrors[problem]); if (description == nullptr) { description = String::Format(CultureInfo::CurrentCulture, "Unknown certificate error - 0x{0:x8}", problem); }
return description; }
public: // The following method is invoked by the CertificateValidationDelegate. static 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 false; }
static void RunClient(String^ machineName, String^ serverName) { // Create a TCP/IP client socket. machineName is the host running the server application. Console::WriteLine("Creating a TCP/IP client socket..."); TcpClient^ client = gcnew TcpClient(machineName, 8080); Console::WriteLine("Client got connected...");
// Create an SSL stream that will close the client's stream. Console::WriteLine("Creating an SSL stream that will close the client\'s stream..."); SslStream^ sslStream = gcnew SslStream( client->GetStream(), false, gcnew RemoteCertificateValidationCallback(ValidateServerCertificate), nullptr);
// The server name must match the name on the server certificate. Console::WriteLine("Server name matching..."); try { sslStream->AuthenticateAsClient(serverName); } catch (AuthenticationException^ ex) { Console::WriteLine("Exception: {0}", ex->Message); if (ex->InnerException != nullptr) { Console::WriteLine("Inner exception: {0}", ex->InnerException->Message); }
Console::WriteLine("Authentication failed - closing the connection."); sslStream->Close(); client->Close(); return; } // Encode a test message into a byte array. Signal the end of the message using the "<EOF>". Console::WriteLine("Do some encoding..."); array<Byte>^ messsage = Encoding::UTF8->GetBytes("Client: Hello from the client.<EOF>");
// Send hello message to the server. Console::WriteLine("Client: Sending all my client luv to youuuu..."); sslStream->Write(messsage); sslStream->Flush(); // Read message from the server. Console::WriteLine("Client: Reading all my luv message from the server..."); String^ serverMessage = ReadMessage(sslStream); Console::WriteLine("My luv Server says: {0}", serverMessage);
// Close the client connection. sslStream->Close(); client->Close(); Console::WriteLine("Client ssl stream closed..."); } private: static String^ ReadMessage(SslStream^ sslStream) {
// Read the message sent by the server. The end of the message is signaled using the "<EOF>" marker. array<Byte>^ buffer = gcnew array<Byte>(2048); StringBuilder^ messageData = gcnew StringBuilder; // Use Decoder class to convert from bytes to UTF8 in case a character spans two buffers. Encoding^ u8 = Encoding::UTF8; Decoder^ decoder = u8->GetDecoder();
int bytes = -1; do { bytes = sslStream->Read(buffer, 0, buffer->Length); array<__wchar_t>^ chars = gcnew array<__wchar_t>(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(); } };
int main(array<System::String ^> ^args) { args = Environment::GetCommandLineArgs(); String^ serverCertificateName = nullptr; String^ machineName = nullptr;
if (args == nullptr || args->Length < 2) { Console::WriteLine("To start the client specify:"); Console::WriteLine("{0} machineName [serverName]", args[0]); return 1; }
// User can specify the machine name and server name. Server name must match the name on the server's certificate. machineName = args[1]; if (args->Length < 3) { serverCertificateName = machineName; } else { serverCertificateName = args[2]; }; SslTcpClient::RunClient(machineName, serverCertificateName); return 0; }
|
Build and run the project. The following are output examples.
Next, let test the server and client program. Firstly, run the server program with valid certificate file and the following just an example that using a sample CER file for Windows Mobile.
Then, run the client program.
Next, we change the port to 443 (SSL port) and rebuild our program. Then we test this client against known SSL server. The following are output examples.
|
|
The following code example demonstrates creating a TcpListener that uses the SslStream class to communicate with clients. Create a new console application project. You can use the solution and project name as shown in the following Figure.
Add the following code.
using System; using System.Collections.Generic; using System.Text; using System.Collections; using System.Net; using System.Net.Security; using System.Net.Sockets; using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; using System.IO;
namespace SslStreamClientCS { 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) { 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, 443); 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("executable_name 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); return 0; } } } |
An output sample:
The following sample output shows the client and server in action.