如何在C#中找出我的服务器名称服务器认证通过客户端(How do I identify my ser

2019-07-31 04:16发布

我最近一直在试图在C#中的SSL加密的服务器/客户端。

我按照这个 MSDN上的教程,但是,它需要一个证书使用的服务器和客户端使用makecert.exe所以我找到了一个例子,它创建的证书罚款要创建:

makecert - SR LOCALMACHINE -ss我-n “CN =测试” -sky交换-SK 123456个C:/Test.cer

但现在的问题是在服务器启动并等待客户,当客户端连接它使用的机器名称 ,其据我所知是我在这种情况下,IP:

127.0.0.1

,然后它要求服务器名称必须与证书(Test.cer)上的服务器名称相匹配。 我曾尝试多种组合(如“测试”“LOCALMACHINE”,“127.0.0.1”,但不能似乎得到给定服务器名称的客户相匹配从而使连接我得到的错误是:

证书错误:RemoteCertificateNameMismatch,RemoteCertificateChainErrors例外:远程证书根据验证过程是无效的

这里是我使用它从MSDN例中,只有我分配在应用程序和客户端过的机器名和服务器名的服务器证书路径的事实不同的代码:

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();
        }
    }
}

编辑:

服务器接受客户端的连接和一切,但它超时,同时等待客户端发送消息。 (客户端不会与服务器进行身份验证因证书中的服务器名称是从我在客户端提供的一个不同),以及多数民众赞成在它我的想法只是为了澄清

更新:

据一个答案我已经改变了certficiate制作者:

makecert - SR LOCALMACHINE -ss我-n “CN = localhost” 的-sky交换-SK 123456个C:/Test.cer和我的客户,我有:

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

现在我得到异常:

RemoteCertificateChainErrors例外:远程证书根据验证过程是无效的

这是这里存在的:

  // 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;
            }  

Answer 1:

答案可以在这里找到SslStream.AuthenticateAsClient方法说明部分:

对于targetHost指定的值必须在服务器证书的名称相匹配。

如果您使用的服务器是谁的主题证书是“CN = localhost”,则必须调用AuthenticateAsClient用“localhost”作为targetHost参数来成功地验证它在客户端。 如果你会使用“CN =大卫-PC”作为证书主题比你必须调用AuthenticateAsClient了“David-PC”作为targetHost。 SslStream通过匹配您打算连接(和你传递给AuthenticateAsClient)与证书中的主题从服务器接收到的服务器名称检查服务器标识。 这种做法是在运行服务器的机器名证书的主体的名称相匹配,并在客户端传递相同的主机名到AuthenticateAsClient因为你已经用于打开(在这种情况下,与TcpClient的)的连接。

但也有其他条件成功建立服务器和客户端之间的SSL连接:证书传递给AuthenticateAsServer必须有一个私钥,必须在客户机上是可信的,不得有涉及使用用于建立SSL会话的任意键的使用限制。

现在相关的代码示例,你的问题是相关的生成和证书的使用。

  • 你是不是对您的证书提供的发行人以这种方式不能被信任 - 这是RemoteCertificateChainErrors异常的原因。 我建议创造发展的目的指定makecert的-r选项自签名证书。

  • 被信任的证书必须是自签名,并放置在Windows证书存储区可信位置或必须签名链到一个已经信任的证书颁发机构进行联系。 因此,而不是在-ss我的选择,这将使该证书在个人存储使用-ss根,将其放置在受信任的根证书颁发机构,它将你的机器上是可信的(从代码,我认为你的客户端运行的在同一台机器与所述服务器并且还在其上生成的证书)上。

  • 如果指定输出文件makecert将证书导出为.CER但这种格式只包含公开密钥,而不是由服务器需要建立一个SSL连接的私钥。 最简单的方法是阅读从Windows证书存储在服务器代码证书。 (你可以从商店中,一个使得存储为这里所描述的私有密钥格式还导出导出证书使用私钥和读取服务器的代码文件)。

你可以找到关于这里使用的makecert选项的详细信息证书创建工具(Makecert.exe)

总之你的代码需要进行以下更改运行(与你的最新代码更新测试):

  • 使用以下命令生成证书:

makecert -SR LOCALMACHINE -ss根-r -n “CN = localhost” 的-sky交换-SK 123456

  • 阅读从Windows证书存储区,而不是文件中的证书(在这个例子中的简单性),所以更换

serverCertificate = X509Certificate.CreateFromCertFile(证书);

在服务器代码:

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];
}

请记得,你打算如果以后改变代码(在这种情况下应该是相同的值传递给makecert -n选项)使用证书的主题来代替“CN = localhost”的。 还要考虑到使用运行的服务器,而不是本地主机的服务器证书的主题机器名。



Answer 2:

服务器证书的CN必须是完全一样的服务器的域名。 我想,在你的情况下,通用名称必须是“本地主机”(W / O引号)。

重要提示:肯定的,因为你可能在其他的答案已经阅读,从来没有使用CN="localhost"生产。



Answer 3:

首先,不要创建的主题为“CN =本地主机”或同等学历证书。 它永远不会在生产中使用,所以不要做。 它总是颁发给计算机的主机名,如CN =“我的电脑”,并连接到它,而不是本地主机时使用的主机名。 您可以使用“主题备用名称”扩展指定多个名称,但makecert似乎并不支持它。

其次,发行服务器SSL证书时,你需要在“服务器身份验证” OID添加到证书的增强型密钥使用(EKU)扩展。 添加-eku 1.3.6.1.5.5.7.3.1参数makecert在你的榜样。 如果你想要做的客户端证书身份验证,使用的1.3.6.1.5.5.7.3.2“客户身份验证” OID。

最后,由makecert创建的默认证书使用MD5作为哈希算法。 MD5被认为是不安全的,虽然它不会影响您的测试,进入使用SHA1的习惯。 添加-a sha1makecert以上参数强制SHA1。 默认密钥长度也应该从1024位增加到2048位,但你的想法。



Answer 4:

为了得到这个与WCF工作,这里有必要先创建一个自签名的根颁发机构的证书,然后用它来创建localhost的证书。

我想同样可能适用于您的项目还有,请看看这篇文章如何为使用创建临时证书发展过程中的细节。



Answer 5:

你有没有尝试过:?

创建一个完整的域名,如证书example.net (这是很好的使用example.netexample.comexample.org任何东西那是故意不真名),或将在现场演出中使用的名称,如果这是一个单一的网站,你知道它是什么。

更新您的hosts文件,它将使用127.0.0.1该名称。



Answer 6:

关于你的更新:

其中SslStream构造函数允许你提供一个RemoteCertificateValidationCallback代表 。 您应该能够把一个断点您提供看什么你得到实际的错误是方法。 检查SslPolicyErrors在送价值。



文章来源: How do I identify my server name for server authentication by client in c#