socket multithreaded multiples multiple clients clientes c# sslstream makecert

multiples - multithreaded tcp server c#



¿Cómo identifico el nombre de mi servidor para la autenticación del servidor por cliente en c# (6)

El CN del certificado de un servidor debe ser exactamente el mismo que el nombre de dominio del servidor. Supongo que, en su caso, el nombre común debe ser "localhost" (sin comillas).

Importante: seguro, como es posible que haya leído en otras respuestas, nunca use CN="localhost" en producción.

Recientemente he estado tratando de hacer un servidor / cliente cifrado SSL en C #.

He seguido this tutorial en MSDN, sin embargo, requería que se creara un certificado para el uso del servidor y del cliente usando makecert.exe, por lo que encontré un ejemplo y creo que el certificado estaba bien:

makecert -sr LocalMachine -ss Mi -n "CN = Prueba" -sky exchange -sk 123456 c: /Test.cer

pero ahora el problema es que el servidor se inicia y espera a los clientes, cuando el cliente se conecta, utiliza el nombre de la máquina que, en la medida de lo posible, es mi IP en este caso:

127.0.0.1

y luego requiere el nombre del servidor que debe coincidir con el nombre del servidor en el certificado ( Test.cer ). He intentado varias combinaciones (como "Prueba" "LocalMachine", "127.0.0.1" pero parece que el nombre del servidor dado a los clientes no coincide, permitiendo la conexión. El error que obtengo es:

Error del certificado: RemoteCertificateNameMismatch, RemoteCertificateChainErrors Excepción: el certificado remoto no es válido de acuerdo con el procedimiento de validación

Aquí está el código que lo estoy usando difiere del ejemplo de MSDN solo en el hecho de que asigno la ruta del certificado para el servidor en la aplicación y también el nombre de la máquina y el nombre del servidor del cliente:

SslTcpServer.cs

using System; using System.Collections; using System.Net; using System.Net.Sockets; using System.Net.Security; using System.Security.Authentication; using System.Text; using System.Security.Cryptography.X509Certificates; using System.IO; namespace Examples.System.Net { public sealed class SslTcpServer { static X509Certificate serverCertificate = null; // The certificate parameter specifies the name of the file // containing the machine certificate. public static void RunServer(string certificate) { serverCertificate = X509Certificate.CreateFromCertFile(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, false, SslProtocols.Tls, 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.<EOF>"); 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 void Main(string[] args) { string certificate = "c:/Test.cer"; SslTcpServer.RunServer(certificate); } } }

SslTcpClient.cs

using System; using System.Collections; using System.Net; using System.Net.Security; using System.Net.Sockets; using System.Security.Authentication; using System.Text; using System.Security.Cryptography.X509Certificates; using System.IO; namespace Examples.System.Net { 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, 8080); 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(); } public static void Main(string[] args) { string serverCertificateName = null; string machineName = null; /* // 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]; }*/ machineName = "127.0.0.1"; serverCertificateName = "David-PC";// tried Test, LocalMachine and 127.0.0.1 SslTcpClient.RunClient(machineName, serverCertificateName); Console.ReadKey(); } } }

EDITAR:

El servidor acepta la conexión de los clientes y todo, pero se agota mientras espera a que el cliente envíe un mensaje. (El cliente no se autentificará con el servidor debido a que el nombre del servidor en el certificado es diferente del que proporcioné en el cliente).

ACTUALIZAR:

Según una respuesta he cambiado el fabricante de certficiado a:

makecert -sr LocalMachine -ss Mi -n "CN = localhost" -sky exchange -sk 123456 c: /Test.cer y en mi cliente tengo:

machineName = "127.0.0.1"; serverCertificateName = "localhost";// tried Test, LocalMachine and 127.0.0.1 SslTcpClient.RunClient(machineName, serverCertificateName);

ahora tengo la excepción:

Excepción de RemoteCertificateChainErrors: el certificado remoto no es válido de acuerdo con el procedimiento de validación

que está ocurriendo aquí:

// 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. "+ e.Message); client.Close(); return; }



Has probado:?

Cree el certificado para un nombre de dominio completo como example.net (es bueno usar example.net , example.com o example.org para cualquier cosa que no sea deliberadamente un nombre real) o el nombre que se usará en uso en vivo si ese es un Solo sitio y ya sabes lo que será.

Actualice su archivo de hosts para que use 127.0.0.1 para ese nombre.


La respuesta se puede encontrar en la sección de observaciones del método SslStream.AuthenticateAsClient :

El valor especificado para targetHost debe coincidir con el nombre en el certificado del servidor.

Si usa para el servidor un certificado cuyo sujeto es "CN = localhost", debe llamar a AuthenticateAsClient con "localhost" como parámetro targetHost para autenticarlo correctamente en el lado del cliente. Si usaría "CN = David-PC" como sujeto del certificado, debe llamar a AuthenticateAsClient con "David-PC" como targetHost. SslStream comprueba la identidad del servidor haciendo coincidir el nombre del servidor que pretende conectar (y el cual pasa a AuthenticateAsClient) con el asunto en el certificado recibido del servidor. La práctica es que el nombre de la máquina que ejecuta el servidor coincide con el nombre del sujeto del certificado, y en el cliente pasa el mismo nombre de host a AuthenticateAsClient que ha utilizado para abrir la conexión (con TcpClient en este caso).

Sin embargo, existen otras condiciones para establecer con éxito la conexión SSL entre servidores y clientes: el certificado pasado a AuthenticateAsServer debe tener una clave privada, debe ser confiable en la máquina cliente y no debe tener ninguna restricción de uso clave relacionada con el uso para establecer sesiones SSL.

Ahora relacionado con su ejemplo de código, su problema está relacionado con la generación y el uso del certificado.

  • No está proporcionando un emisor para su certificado y, de este modo, no se puede confiar en él: esta es la causa de la excepción RemoteCertificateChainErrors. Sugiero crear un certificado autofirmado para fines de desarrollo especificando la opción -r de makecert.

  • Para ser de confianza, un certificado debe ser autofirmado y colocado en una ubicación de confianza en el Almacén de certificados de Windows o debe estar vinculado con una cadena de firmas a una Autoridad de Certificación ya confiable. Entonces, en lugar de la opción -ss Mi que colocará el certificado en el Almacén personal, use la raíz -ss que lo colocará en las Autoridades de Certificación de Raíces Confiables y se confiará en su máquina (a partir del código asumo que su cliente se está ejecutando). en la misma máquina con el servidor y también se genera el certificado en él).

  • Si especifica un archivo de salida para makecert, exportará el certificado como .cer pero este formato solo contiene la clave pública, no la clave privada que necesita el servidor para establecer una conexión SSL. La forma más sencilla es leer el certificado del almacén de certificados de Windows en el código del servidor. (También puede exportarlo desde la tienda en otro formato que permita almacenar la clave privada como se describe aquí. Exporte un certificado con la clave privada y lea ese archivo en el código del servidor).

Puede encontrar detalles sobre las opciones de makecert usadas aquí Herramienta de creación de certificados (Makecert.exe)

En conclusión, su código necesita los siguientes cambios para ejecutarse (probado con sus últimas actualizaciones de código):

  • Use el siguiente comando para generar el certificado:

makecert -sr LocalMachine -ss root -r -n "CN = localhost" -sky exchange -sk 123456

  • Lea el certificado del Almacén de certificados de Windows en lugar de un archivo (por la simplicidad de este ejemplo), así que reemplace

serverCertificate = X509Certificate.CreateFromCertFile (certificado);

en el código del servidor con:

X509Store store = new X509Store(StoreName.Root, StoreLocation.LocalMachine); store.Open(OpenFlags.ReadOnly); var certificates = store.Certificates.Find(X509FindType.FindBySubjectDistinguishedName, "CN=localhost", false); store.Close(); if (certificates.Count == 0) { Console.WriteLine("Server certificate not found..."); return; } else { serverCertificate = certificates[0]; }

Recuerde reemplazar "CN = localhost" con el asunto del certificado que pretende utilizar si cambia el código más adelante (en esta situación, debería tener el mismo valor que la opción -n pasada a makecert). También considere usar el nombre de la máquina que ejecuta el servidor en lugar de localhost en el asunto del certificado del servidor.


Para que esto funcione con WCF, es necesario crear primero un certificado de autoridad raíz autofirmado y luego usarlo para crear el certificado para localhost.

Pienso que lo mismo podría aplicarse a su proyecto, por favor, lea este artículo Cómo: Crear certificados temporales para usar durante el desarrollo para obtener más detalles.


Primero, no cree un certificado con el asunto "CN = localhost" o equivalente. Nunca se va a utilizar en la producción, así que no lo hagas. Cámbielo siempre al nombre de host de su computadora, por ejemplo, CN = "mycomputer", y use el nombre de host cuando se conecte a él en lugar de a localhost. Puede especificar varios nombres utilizando la extensión "nombre alternativo del sujeto" pero makecert no parece makecert .

En segundo lugar, al emitir un certificado SSL de servidor, debe agregar el OID de "autenticación de servidor" a la extensión de uso de clave mejorada (EKU) del certificado. Agregue el parámetro -eku 1.3.6.1.5.5.7.3.1 a makecert en su ejemplo. Si desea realizar la autenticación de certificado de cliente, use el OID "autenticación de cliente" de 1.3.6.1.5.5.7.3.2.

Por último, el certificado predeterminado creado por makecert utiliza MD5 como su algoritmo de hash. MD5 se considera inseguro y, aunque no afectará sus pruebas, adquiera el hábito de usar SHA1. Agregue -a sha1 a los parámetros de makecert anteriores para forzar SHA1. El tamaño de clave predeterminado también debe aumentarse de 1024 bits a 2048 bits, pero se entiende la idea.