简体   繁体   English

在TLS Web套接字服务器中使用SslStream时出现问题

[英]Problems using SslStream in a TLS web socket server

I followed this example to create my test certificates. 我按照此示例创建了我的测试证书。 I used Certificate.cer for the server and Certificate.pfx for the client: 我将Certificate.cer用于服务器,并将Certificate.pfx用于客户端:

makecert -r -pe -n "CN=Test Certificate" -sky exchange Certificate.cer -sv Key.pvk -eku 1.3.6.1.5.5.7.3.1,1.3.6.1.5.5.7.3.2

"C:\\Program Files (x86)\\Windows Kits\\8.1\\bin\\x64\\pvk2pfx.exe" -pvk Key.pvk -spc Certificate.cer -pfx Certificate.pfx

I am trying to create a web socket server and properly validate certificates from both the client and server sides of the communication. 我正在尝试创建Web套接字服务器,并从通信的客户端和服务器端正确验证证书。 Here is my entire console application which I am currently building: 这是我当前正在构建的整个控制台应用程序:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Net.WebSockets;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;

namespace WebSockets
{    
    class Program
    {
        static void Main(string[] args)
        {
            CreateWebSocketClient(CreateWebSocketServer(1337), 1338);
            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();
        }

        private static IPEndPoint CreateWebSocketServer(int port)
        {
            var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);
            IPEndPoint endpoint = new IPEndPoint(IPAddress.Loopback, port);
            socket.Bind(endpoint);
            socket.Listen(Int32.MaxValue);
            socket.BeginAccept((result) =>
            {
                var clientSocket = socket.EndAccept(result);
                Console.WriteLine("{0}: Connected to the client at {1}.", DateTime.Now, clientSocket.RemoteEndPoint);
                using (var stream = new SslStream(new NetworkStream(clientSocket), false, (sender, certificate, chain, sslPolicyErrors) =>
                    {
                        return true;
                    }, (sender, targetHost, localCertificates, remoteCertificate, acceptableIssuers) =>
                    {
                        return new X509Certificate2("Certificate.pfx");
                    }, EncryptionPolicy.RequireEncryption))
                {
                    stream.AuthenticateAsServer(new X509Certificate2("Certificate.pfx"), true, SslProtocols.Tls12, true);
                    stream.Write("Hello".ToByteArray());
                    Console.WriteLine("{0}: Read \"{1}\" from the client at {2}.", DateTime.Now, stream.ReadMessage(), clientSocket.RemoteEndPoint);
                }
            }, null);
            Console.WriteLine("{0}: Web socket server started at {1}.", DateTime.Now, socket.LocalEndPoint);
            return endpoint;
        }

        private static void CreateWebSocketClient(IPEndPoint remoteEndpoint, int port)
        {
            var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);
            IPEndPoint localEndpoint = new IPEndPoint(IPAddress.Loopback, port);
            socket.Bind(localEndpoint);
            socket.BeginConnect(remoteEndpoint, (result) =>
            {
                socket.EndConnect(result);
                Console.WriteLine("{0}: Connected to the server at {1}.", DateTime.Now, remoteEndpoint);
                using (var stream = new SslStream(new NetworkStream(socket), false, (sender, certificate, chain, sslPolicyErrors) =>
                    {
                        return true;
                    }, (sender, targetHost, localCertificates, remoteCertificate, acceptableIssuers) =>
                    {
                        return new X509Certificate2("Certificate.cer");
                    }, EncryptionPolicy.RequireEncryption))
                {
                    stream.AuthenticateAsClient(remoteEndpoint.ToString(), new X509Certificate2Collection(new X509Certificate2[] { new X509Certificate2("Certificate.cer") }), SslProtocols.Tls12, true);
                    stream.Write("Hello".ToByteArray());
                    Console.WriteLine("{0}: Read \"{1}\" from the server at {2}.", DateTime.Now, stream.ReadMessage(), remoteEndpoint);
                }
            }, null);
        }
    }

    public static class StringExtensions
    {
        public static Byte[] ToByteArray(this String value)
        {
            Byte[] bytes = new Byte[value.Length * sizeof(Char)];
            Buffer.BlockCopy(value.ToCharArray(), 0, bytes, 0, bytes.Length);
            return bytes;
        }

        public static String FromByteArray(this Byte[] bytes)
        {
            Char[] characters = new Char[bytes.Length / sizeof(Char)];
            Buffer.BlockCopy(bytes, 0, characters, 0, bytes.Length);
            return new String(characters).Trim(new Char[] { (Char)0 });
        }

        public static int BufferSize = 0x400;

        public static String ReadMessage(this SslStream stream)
        {
            var buffer = new Byte[BufferSize];
            stream.Read(buffer, 0, BufferSize);
            return FromByteArray(buffer);
        }
    }
}

Communication between server and client works fine when you run it, but I am not sure how I should implement the callbacks, specifically because sslPolicyErrors = RemoteCertificateNotAvailable when the RemoteCertificateValidationCallback is called on the server side and sslPolicyErrors = RemoteCertificateNameMismatch | RemoteCertificateChainErrors 当你运行它的服务器和客户端之间的通讯正常,但我不知道我应该如何实现的回调,特别是因为sslPolicyErrors = RemoteCertificateNotAvailableRemoteCertificateValidationCallback被称为服务器端和sslPolicyErrors = RemoteCertificateNameMismatch | RemoteCertificateChainErrors sslPolicyErrors = RemoteCertificateNameMismatch | RemoteCertificateChainErrors when the RemoteCertificateValidationCallback is called on the client side. 在客户端调用sslPolicyErrors = RemoteCertificateNameMismatch | RemoteCertificateChainErrors时发生RemoteCertificateChainError Also, certificate and chain are null on the server side but appear on the callback from the client side. 此外, certificatechain在服务器端为空,但出现在客户端的回调中。 Why is that? 这是为什么? What are the problems with my implementation and how can I make my implementation validate SSL certificates properly? 我的实施存在什么问题,如何使我的实施正确验证SSL证书? I have tried searching online about the SslStream but I have yet to see a full, X509-based TLS server-client implementation that does the type of certificate validation I need. 我曾尝试在线搜索有关SslStream但还没有看到完整的基于X509的TLS服务器-客户端实现,该实现可以执行我需要的证书验证类型。

I had three separate problems. 我有三个独立的问题。 My initial approach was good, but: 我最初的方法很好,但是:

  1. I have misused certificates here, as using the .pfx certificate on the client side resolves my RemoteCertificateNotAvailable problem. 我在这里滥用了证书,因为在客户端使用.pfx证书可以解决我的RemoteCertificateNotAvailable问题。 I am not sure as to why the .cer did not work. 我不确定.cer为什么不起作用。

  2. I have specified the wrong subject name in my call to AuthenticateAsClient , as using "Test Certificate" for the first argument instead of remoteEndpoint.ToString() solves my RemoteCertificateNameMismatch . 我在对AuthenticateAsClient调用中指定了错误的主题名称,因为对第一个参数使用“ Test Certificate”代替了remoteEndpoint.ToString()解决了我的RemoteCertificateNameMismatch

  3. Despite being self-signed, to get around the RemoteCertificateChainErrors error, I had to add this certificate to the Trusted People store under my current user account in order to trust the certificate. 尽管是自签名的,但要解决RemoteCertificateChainErrors错误,我必须将此证书添加到当前用户帐户下的“受信任的人”存储中,以便信任该证书。

Some other small refinements included, and my resulting code, which accepts multiple clients now as well (as I had fixed some bugs above), is as follows ( please don't copy this verbatim as it needs a lot of Pokemon exception handling in different places, proper clean-up logic, making use of the bytes read on Read calls instead of trimming NUL, and the introduction of some Unicode character such as EOT to specify the end of messages, parsing for it, as well as handling of odd sized buffers which are not supported since our C# character size is 2 bytes, handling of odd reads, etc.; this needs a lot of refinement before it ever sees the light of a production system and serves only as an example or a proof of concept, if you will. ): 包括一些其他小的改进,并且我得到的代码现在也接受多个客户端(如我在上面已修复的一些错误),如下( 请不要复制此逐字记录,因为它需要在不同的地方进行很多Pokemon异常处理位置,适当的清理逻辑,利用在Read调用中读取的字节而不是修剪NUL,并引入一些Unicode字符(例如EOT)以指定消息的末尾,对其进行解析以及对奇数大小的处理由于我们的C#字符大小为2个字节,处理奇数读取等,因此不支持这些缓冲区;这需要很多改进,才能看到生产系统,并且仅用作示例或概念证明,如果可以的话 ):

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Net.WebSockets;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace WebSockets
{
    class Program
    {
        static void Main(string[] args)
        {
            IPEndPoint server = CreateWebSocketServer(1337);
            CreateWebSocketClient(server, 1338);
            CreateWebSocketClient(server, 1339);
            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();
        }

        private static IPEndPoint CreateWebSocketServer(int port)
        {
            var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);
            IPEndPoint endpoint = new IPEndPoint(IPAddress.Loopback, port);
            socket.Bind(endpoint);
            socket.Listen(Int32.MaxValue);
            ListenForClients(socket);
            Console.WriteLine("{0}: Web socket server started at {1}.", DateTime.Now, socket.LocalEndPoint);
            return endpoint;
        }

        private static void ListenForClients(Socket socket)
        {
            socket.BeginAccept((result) =>
            {
                new Thread(() =>
                {
                    ListenForClients(socket);
                }).Start();
                var clientSocket = socket.EndAccept(result);
                Console.WriteLine("{0}: Connected to the client at {1}.", DateTime.Now, clientSocket.RemoteEndPoint);
                using (var stream = new SslStream(new NetworkStream(clientSocket), false, (sender, certificate, chain, sslPolicyErrors) =>
                {
                    if (sslPolicyErrors == SslPolicyErrors.None)
                        return true;
                    return false;
                }, (sender, targetHost, localCertificates, remoteCertificate, acceptableIssuers) =>
                {
                    return new X509Certificate2("Certificate.pfx");
                }, EncryptionPolicy.RequireEncryption))
                {
                    stream.AuthenticateAsServer(new X509Certificate2("Certificate.pfx"), true, SslProtocols.Tls12, true);
                    stream.Write("Hello".ToByteArray());
                    Console.WriteLine("{0}: Read \"{1}\" from the client at {2}.", DateTime.Now, stream.ReadMessage(), clientSocket.RemoteEndPoint);
                }
            }, null);
        }

        private static void CreateWebSocketClient(IPEndPoint remoteEndpoint, int port)
        {
            var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);
            IPEndPoint localEndpoint = new IPEndPoint(IPAddress.Loopback, port);
            socket.Bind(localEndpoint);
            socket.BeginConnect(remoteEndpoint, (result) =>
            {
                socket.EndConnect(result);
                Console.WriteLine("{0}: Client at {1} connected to the server at {2}.", DateTime.Now, localEndpoint, remoteEndpoint);
                using (var stream = new SslStream(new NetworkStream(socket), false, (sender, certificate, chain, sslPolicyErrors) =>
                {
                    if (sslPolicyErrors == SslPolicyErrors.None)
                        return true;
                    return false;
                }, (sender, targetHost, localCertificates, remoteCertificate, acceptableIssuers) =>
                {
                    return new X509Certificate2("Certificate.pfx");
                }, EncryptionPolicy.RequireEncryption))
                {
                    stream.AuthenticateAsClient("Test Certificate", new X509Certificate2Collection(new X509Certificate2[] { new X509Certificate2("Certificate.pfx") }), SslProtocols.Tls12, true);
                    stream.Write("Hello".ToByteArray());
                    Console.WriteLine("{0}: Client at {1} read \"{2}\" from the server at {3}.", DateTime.Now, localEndpoint, stream.ReadMessage(), remoteEndpoint);
                }
            }, null);
        }
    }

    public static class StringExtensions
    {
        public static Byte[] ToByteArray(this String value)
        {
            Byte[] bytes = new Byte[value.Length * sizeof(Char)];
            Buffer.BlockCopy(value.ToCharArray(), 0, bytes, 0, bytes.Length);
            return bytes;
        }

        public static String FromByteArray(this Byte[] bytes)
        {
            Char[] characters = new Char[bytes.Length / sizeof(Char)];
            Buffer.BlockCopy(bytes, 0, characters, 0, bytes.Length);
            return new String(characters).Trim(new Char[] { (Char)0 });
        }

        public static int BufferSize = 0x400;

        public static String ReadMessage(this SslStream stream)
        {
            var buffer = new Byte[BufferSize];
            stream.Read(buffer, 0, BufferSize);
            return FromByteArray(buffer);
        }
    }
}

I hope this helps others demystify web sockets, SSL streams, X509 certificates, and so forth, in C#. 我希望这可以帮助其他人在C#中使Web套接字,SSL流,X509证书等神秘化。 Happy coding. 快乐的编码。 :) I may end up posting its final edition on my blog. :)我最终可能会在其博客上发布其最终版本。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM